/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.goide.psi.impl;
import com.goide.GoConstants;
import com.goide.psi.*;
import com.goide.util.GoUtil;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.util.*;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.OrderedSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import static com.goide.psi.impl.GoPsiImplUtil.*;
public class GoReference extends GoReferenceBase<GoReferenceExpressionBase> {
private static final Key<Object> POINTER = Key.create("POINTER");
private static final Key<Object> DONT_PROCESS_METHODS = Key.create("DONT_PROCESS_METHODS");
private static final ResolveCache.PolyVariantResolver<GoReference> MY_RESOLVER =
(r, incompleteCode) -> r.resolveInner();
public GoReference(@NotNull GoReferenceExpressionBase o) {
super(o, TextRange.from(o.getIdentifier().getStartOffsetInParent(), o.getIdentifier().getTextLength()));
}
@Nullable
static PsiFile getContextFile(@NotNull ResolveState state) {
PsiElement element = getContextElement(state);
return element != null ? element.getContainingFile() : null;
}
@NotNull
private ResolveResult[] resolveInner() {
if (!myElement.isValid()) return ResolveResult.EMPTY_ARRAY;
Collection<ResolveResult> result = new OrderedSet<>();
processResolveVariants(createResolveProcessor(result, myElement));
return result.toArray(new ResolveResult[result.size()]);
}
@Override
public boolean isReferenceTo(@NotNull PsiElement element) {
return GoUtil.couldBeReferenceTo(element, myElement) && super.isReferenceTo(element);
}
@NotNull
private PsiElement getIdentifier() {
return myElement.getIdentifier();
}
@Override
@NotNull
public ResolveResult[] multiResolve(boolean incompleteCode) {
if (!myElement.isValid()) return ResolveResult.EMPTY_ARRAY;
return ResolveCache.getInstance(myElement.getProject()).resolveWithCaching(this, MY_RESOLVER, false, false);
}
@NotNull
@Override
public Object[] getVariants() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
public boolean processResolveVariants(@NotNull GoScopeProcessor processor) {
PsiFile file = myElement.getContainingFile();
if (!(file instanceof GoFile)) return false;
ResolveState state = createContextOnElement(myElement);
GoReferenceExpressionBase qualifier = myElement.getQualifier();
return qualifier != null
? processQualifierExpression((GoFile)file, qualifier, processor, state)
: processUnqualifiedResolve((GoFile)file, processor, state);
}
private boolean processQualifierExpression(@NotNull GoFile file,
@NotNull GoReferenceExpressionBase qualifier,
@NotNull GoScopeProcessor processor,
@NotNull ResolveState state) {
PsiReference reference = qualifier.getReference();
PsiElement target = reference != null ? reference.resolve() : null;
if (target == null) return false;
if (target == qualifier) return processor.execute(myElement, state);
if (target instanceof GoImportSpec) {
if (((GoImportSpec)target).isCImport()) return processor.execute(myElement, state);
target = ((GoImportSpec)target).getImportString().resolve();
}
if (target instanceof PsiDirectory && !processDirectory((PsiDirectory)target, file, null, processor, state, false)) return false;
if (target instanceof GoTypeOwner) {
GoType type = typeOrParameterType((GoTypeOwner)target, createContextOnElement(myElement));
if (type instanceof GoCType) return processor.execute(myElement, state);
if (type != null) {
if (!processGoType(type, processor, state)) return false;
GoTypeReferenceExpression ref = getTypeRefExpression(type);
if (ref != null && ref.resolve() == ref) return processor.execute(myElement, state); // a bit hacky resolve for: var a C.foo; a.b
}
}
return true;
}
private boolean processGoType(@NotNull GoType type, @NotNull GoScopeProcessor processor, @NotNull ResolveState state) {
Boolean result = RecursionManager.doPreventingRecursion(type, true, () -> {
if (type instanceof GoParType) return processGoType(((GoParType)type).getActualType(), processor, state);
if (!processExistingType(type, processor, state)) return false;
if (type instanceof GoPointerType) {
if (!processPointer((GoPointerType)type, processor, state.put(POINTER, true))) return false;
GoType pointer = ((GoPointerType)type).getType();
if (pointer instanceof GoPointerType) {
return processPointer((GoPointerType)pointer, processor, state.put(POINTER, true));
}
}
return processTypeRef(type, processor, state);
});
return Boolean.TRUE.equals(result);
}
private boolean processPointer(@NotNull GoPointerType type, @NotNull GoScopeProcessor processor, @NotNull ResolveState state) {
GoType pointer = type.getType();
return pointer == null || processExistingType(pointer, processor, state) && processTypeRef(pointer, processor, state);
}
private boolean processTypeRef(@Nullable GoType type, @NotNull GoScopeProcessor processor, @NotNull ResolveState state) {
if (type == null) {
return true;
}
if (builtin(type)) {
// do not process builtin types like 'int int' or 'string string'
return true;
}
return processInTypeRef(type.getTypeReferenceExpression(), processor, state);
}
private boolean processExistingType(@NotNull GoType type, @NotNull GoScopeProcessor processor, @NotNull ResolveState state) {
PsiFile file = type.getContainingFile();
if (!(file instanceof GoFile)) return true;
PsiFile myFile = ObjectUtils.notNull(getContextFile(state), myElement.getContainingFile());
if (!(myFile instanceof GoFile) || !allowed(file, myFile, ModuleUtilCore.findModuleForPsiElement(myFile))) return true;
boolean localResolve = isLocalResolve(myFile, file);
GoTypeSpec parent = getTypeSpecSafe(type);
boolean canProcessMethods = state.get(DONT_PROCESS_METHODS) == null;
if (canProcessMethods && parent != null && !processNamedElements(processor, state, parent.getMethods(), localResolve, true)) return false;
if (type instanceof GoSpecType) {
type = type.getUnderlyingType();
}
if (type instanceof GoStructType) {
GoScopeProcessorBase delegate = createDelegate(processor);
type.processDeclarations(delegate, ResolveState.initial(), null, myElement);
List<GoTypeReferenceExpression> interfaceRefs = ContainerUtil.newArrayList();
List<GoTypeReferenceExpression> structRefs = ContainerUtil.newArrayList();
for (GoFieldDeclaration d : ((GoStructType)type).getFieldDeclarationList()) {
if (!processNamedElements(processor, state, d.getFieldDefinitionList(), localResolve)) return false;
GoAnonymousFieldDefinition anon = d.getAnonymousFieldDefinition();
GoTypeReferenceExpression ref = anon != null ? anon.getTypeReferenceExpression() : null;
if (ref != null) {
(anon.getType() instanceof GoPointerType ? structRefs : interfaceRefs).add(ref);
if (!processNamedElements(processor, state, ContainerUtil.createMaybeSingletonList(anon), localResolve)) return false;
}
}
if (!processCollectedRefs(interfaceRefs, processor, state.put(POINTER, null))) return false;
if (!processCollectedRefs(structRefs, processor, state)) return false;
}
else if (state.get(POINTER) == null && type instanceof GoInterfaceType) {
if (!processNamedElements(processor, state, ((GoInterfaceType)type).getMethods(), localResolve, true)) return false;
if (!processCollectedRefs(((GoInterfaceType)type).getBaseTypesReferences(), processor, state)) return false;
}
else if (type instanceof GoFunctionType) {
GoSignature signature = ((GoFunctionType)type).getSignature();
GoResult result = signature != null ? signature.getResult() : null;
GoType resultType = result != null ? result.getType() : null;
if (resultType != null && !processGoType(resultType, processor, state)) return false;
}
return true;
}
public static boolean isLocalResolve(@NotNull PsiFile originFile, @NotNull PsiFile externalFile) {
if (!(originFile instanceof GoFile) || !(externalFile instanceof GoFile)) return false;
GoFile o1 = (GoFile)originFile.getOriginalFile();
GoFile o2 = (GoFile)externalFile.getOriginalFile();
return Comparing.equal(o1.getImportPath(false), o2.getImportPath(false))
&& Comparing.equal(o1.getPackageName(), o2.getPackageName());
}
private boolean processCollectedRefs(@NotNull List<GoTypeReferenceExpression> refs,
@NotNull GoScopeProcessor processor,
@NotNull ResolveState state) {
for (GoTypeReferenceExpression ref : refs) {
if (!processInTypeRef(ref, processor, state)) return false;
}
return true;
}
private boolean processInTypeRef(@Nullable GoTypeReferenceExpression e, @NotNull GoScopeProcessor processor, @NotNull ResolveState state) {
PsiElement resolve = e != null ? e.resolve() : null;
if (resolve instanceof GoTypeOwner) {
GoType type = ((GoTypeOwner)resolve).getGoType(state);
if (type == null) return true;
if (!processGoType(type, processor, state)) return false;
if (type instanceof GoSpecType) {
GoType inner = ((GoSpecType)type).getType();
if (inner instanceof GoPointerType && state.get(POINTER) != null) return true;
if (inner != null && !processGoType(inner, processor, state.put(DONT_PROCESS_METHODS, true))) return false;
}
return true;
}
return true;
}
private boolean processUnqualifiedResolve(@NotNull GoFile file,
@NotNull GoScopeProcessor processor,
@NotNull ResolveState state) {
if (getIdentifier().textMatches("_")) return processor.execute(myElement, state);
PsiElement parent = myElement.getParent();
if (parent instanceof GoSelectorExpr) {
boolean result = processSelector((GoSelectorExpr)parent, processor, state, myElement);
if (processor.isCompletion()) return result;
if (!result || prevDot(myElement)) return false;
}
PsiElement grandPa = parent.getParent();
if (grandPa instanceof GoSelectorExpr && !processSelector((GoSelectorExpr)grandPa, processor, state, parent)) return false;
if (prevDot(parent)) return false;
if (!processBlock(processor, state, true)) return false;
if (!processReceiver(processor, state, true)) return false;
if (!processImports(file, processor, state, myElement)) return false;
if (!processFileEntities(file, processor, state, true)) return false;
if (!processDirectory(file.getOriginalFile().getParent(), file, file.getPackageName(), processor, state, true)) return false;
return processBuiltin(processor, state, myElement);
}
private boolean processReceiver(@NotNull GoScopeProcessor processor, @NotNull ResolveState state, boolean localResolve) {
GoScopeProcessorBase delegate = createDelegate(processor);
GoMethodDeclaration method = PsiTreeUtil.getParentOfType(myElement, GoMethodDeclaration.class);
GoReceiver receiver = method != null ? method.getReceiver() : null;
if (receiver == null) return true;
receiver.processDeclarations(delegate, ResolveState.initial(), null, myElement);
return processNamedElements(processor, state, delegate.getVariants(), localResolve);
}
private boolean processBlock(@NotNull GoScopeProcessor processor, @NotNull ResolveState state, boolean localResolve) {
GoScopeProcessorBase delegate = createDelegate(processor);
ResolveUtil.treeWalkUp(myElement, delegate);
return processNamedElements(processor, state, delegate.getVariants(), localResolve);
}
private boolean processSelector(@NotNull GoSelectorExpr parent,
@NotNull GoScopeProcessor processor,
@NotNull ResolveState state,
@Nullable PsiElement another) {
List<GoExpression> list = parent.getExpressionList();
if (list.size() > 1 && list.get(1).isEquivalentTo(another)) {
GoExpression e = list.get(0);
List<GoReferenceExpression> refs = ContainerUtil.newArrayList(PsiTreeUtil.findChildrenOfType(e, GoReferenceExpression.class));
GoExpression o = refs.size() > 1 ? refs.get(refs.size() - 1) : e;
PsiReference ref = o.getReference();
PsiElement resolve = ref != null ? ref.resolve() : null;
if (resolve == o) return processor.execute(myElement, state); // var c = C.call(); c.a.b.d;
GoType type = e.getGoType(createContextOnElement(myElement));
if (type != null && !processGoType(type, processor, state)) return false;
}
return true;
}
@NotNull
private GoVarProcessor createDelegate(@NotNull GoScopeProcessor processor) {
return new GoVarProcessor(getIdentifier(), myElement, processor.isCompletion(), true) {
@Override
protected boolean crossOff(@NotNull PsiElement e) {
if (e instanceof GoFieldDefinition) return true;
return super.crossOff(e) && !(e instanceof GoTypeSpec);
}
};
}
@Override
protected boolean processFileEntities(@NotNull GoFile file,
@NotNull GoScopeProcessor processor,
@NotNull ResolveState state,
boolean localProcessing) {
if (!processNamedElements(processor, state, file.getConstants(), o -> !Comparing.equal(GoConstants.IOTA, o.getName()) ||
!builtin(o) ||
PsiTreeUtil.getParentOfType(getContextElement(state), GoConstSpec.class) != null, localProcessing, false)) return false;
if (!processNamedElements(processor, state, file.getVars(), localProcessing)) return false;
Condition<GoNamedElement> dontProcessInit = o -> o instanceof GoFunctionDeclaration && !Comparing.equal(o.getName(), GoConstants.INIT);
if (!processNamedElements(processor, state, file.getFunctions(), dontProcessInit, localProcessing, false)) return false;
return processNamedElements(processor, state, file.getTypes(), localProcessing);
}
@NotNull
@Override
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
getIdentifier().replace(GoElementFactory.createIdentifierFromText(myElement.getProject(), newElementName));
return myElement;
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof GoReference && getElement() == ((GoReference)o).getElement();
}
@Override
public int hashCode() {
return getElement().hashCode();
}
}