/* * 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.GoTypes; import com.goide.psi.*; import com.goide.sdk.GoSdkUtil; import com.goide.util.GoUtil; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.formatter.FormatterUtil; import com.intellij.psi.impl.source.resolve.ResolveCache; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.OrderedSet; import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.List; import java.util.Set; public class GoTypeReference extends GoReferenceBase<GoTypeReferenceExpression> { private final boolean myInsideInterfaceType; public GoTypeReference(@NotNull GoTypeReferenceExpression o) { super(o, TextRange.from(o.getIdentifier().getStartOffsetInParent(), o.getIdentifier().getTextLength())); myInsideInterfaceType = myElement.getParent() instanceof GoMethodSpec; } private static final ResolveCache.PolyVariantResolver<PsiPolyVariantReferenceBase> MY_RESOLVER = (psiPolyVariantReferenceBase, incompleteCode) -> ((GoTypeReference)psiPolyVariantReferenceBase).resolveInner(); @NotNull private ResolveResult[] resolveInner() { Collection<ResolveResult> result = new OrderedSet<>(); processResolveVariants(createResolveProcessor(result, myElement)); return result.toArray(new ResolveResult[result.size()]); } @Override public boolean isReferenceTo(PsiElement element) { return GoUtil.couldBeReferenceTo(element, myElement) && super.isReferenceTo(element); } @NotNull private PsiElement getIdentifier() { return myElement.getIdentifier(); } @Override @NotNull public ResolveResult[] multiResolve(boolean incompleteCode) { return myElement.isValid() ? ResolveCache.getInstance(myElement.getProject()).resolveWithCaching(this, MY_RESOLVER, false, false) : ResolveResult.EMPTY_ARRAY; } @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 = ResolveState.initial(); GoTypeReferenceExpression qualifier = myElement.getQualifier(); if (qualifier != null) { return processQualifierExpression((GoFile)file, qualifier, processor, state); } return processUnqualifiedResolve((GoFile)file, processor, state, true); } private boolean processQualifierExpression(@NotNull GoFile file, @NotNull GoTypeReferenceExpression qualifier, @NotNull GoScopeProcessor processor, @NotNull ResolveState state) { PsiElement target = qualifier.resolve(); if (target == null || target == qualifier) return false; 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; } private boolean processUnqualifiedResolve(@NotNull GoFile file, @NotNull GoScopeProcessor processor, @NotNull ResolveState state, boolean localResolve) { GoScopeProcessorBase delegate = createDelegate(processor); ResolveUtil.treeWalkUp(myElement, delegate); Collection<? extends GoNamedElement> result = delegate.getVariants(); if (!processNamedElements(processor, state, result, localResolve)) return false; if (!processFileEntities(file, processor, state, localResolve)) return false; PsiDirectory dir = file.getOriginalFile().getParent(); if (!processDirectory(dir, file, file.getPackageName(), processor, state, true)) return false; if (PsiTreeUtil.getParentOfType(getElement(), GoReceiver.class) != null) return true; if (!processImports(file, processor, state, myElement)) return false; if (!processBuiltin(processor, state, myElement)) return false; if (getIdentifier().textMatches(GoConstants.NIL) && PsiTreeUtil.getParentOfType(myElement, GoTypeCaseClause.class) != null) { GoType type = PsiTreeUtil.getParentOfType(myElement, GoType.class); if (FormatterUtil.getPrevious(type != null ? type.getNode() : null, GoTypes.CASE) == null) return true; GoFile builtinFile = GoSdkUtil.findBuiltinFile(myElement); if (builtinFile == null) return false; GoVarDefinition nil = ContainerUtil.find(builtinFile.getVars(), v -> GoConstants.NIL.equals(v.getName())); if (nil != null && !processor.execute(nil, state)) return false; } return true; } public final static Set<String> DOC_ONLY_TYPES = ContainerUtil.set("Type", "Type1", "IntegerType", "FloatType", "ComplexType"); private static final Condition<GoTypeSpec> BUILTIN_TYPE = spec -> { String name = spec.getName(); return name != null && !DOC_ONLY_TYPES.contains(name); }; @NotNull private GoTypeProcessor createDelegate(@NotNull GoScopeProcessor processor) { return new GoTypeProcessor(myElement, processor.isCompletion()); } @Override protected boolean processFileEntities(@NotNull GoFile file, @NotNull GoScopeProcessor processor, @NotNull ResolveState state, boolean localProcessing) { List<GoTypeSpec> types = GoPsiImplUtil.isBuiltinFile(file) ? ContainerUtil.filter(file.getTypes(), BUILTIN_TYPE) : file.getTypes(); return processNamedElements(processor, state, types, localProcessing); } private boolean processNamedElements(@NotNull PsiScopeProcessor processor, @NotNull ResolveState state, @NotNull Collection<? extends GoNamedElement> elements, boolean localResolve) { for (GoNamedElement definition : elements) { if (definition instanceof GoTypeSpec && !allowed((GoTypeSpec)definition)) continue; if ((definition.isPublic() || localResolve) && !processor.execute(definition, state)) return false; } return true; } public boolean allowed(@NotNull GoTypeSpec definition) { return !myInsideInterfaceType || definition.getSpecType().getType() instanceof GoInterfaceType; } @Override public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { getIdentifier().replace(GoElementFactory.createIdentifierFromText(myElement.getProject(), newElementName)); return myElement; } }