| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * 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.jetbrains.python.psi.impl.references; |
| |
| import com.google.common.collect.Lists; |
| import com.intellij.codeInsight.completion.CompletionUtil; |
| import com.intellij.codeInsight.lookup.LookupElement; |
| import com.intellij.codeInsight.lookup.LookupElementBuilder; |
| import com.intellij.lang.ASTNode; |
| import com.intellij.lang.annotation.HighlightSeverity; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.source.resolve.ResolveCache; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.PlatformIcons; |
| import com.intellij.util.ProcessingContext; |
| import com.jetbrains.python.PyNames; |
| import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; |
| import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction; |
| import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; |
| import com.jetbrains.python.codeInsight.dataflow.scope.Scope; |
| import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; |
| import com.jetbrains.python.psi.*; |
| import com.jetbrains.python.psi.impl.*; |
| import com.jetbrains.python.psi.resolve.*; |
| import com.jetbrains.python.psi.types.PyModuleType; |
| import com.jetbrains.python.psi.types.PyType; |
| import com.jetbrains.python.psi.types.TypeEvalContext; |
| import com.jetbrains.python.refactoring.PyDefUseUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * @author yole |
| */ |
| public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference { |
| protected final PyQualifiedExpression myElement; |
| protected final PyResolveContext myContext; |
| |
| public PyReferenceImpl(PyQualifiedExpression element, @NotNull PyResolveContext context) { |
| myElement = element; |
| myContext = context; |
| } |
| |
| @Override |
| public TextRange getRangeInElement() { |
| final ASTNode nameElement = myElement.getNameElement(); |
| final TextRange range = nameElement != null ? nameElement.getTextRange() : myElement.getNode().getTextRange(); |
| return range.shiftRight(-myElement.getNode().getStartOffset()); |
| } |
| |
| @Override |
| public PsiElement getElement() { |
| return myElement; |
| } |
| |
| /** |
| * Resolves reference to the most obvious point. |
| * Imported module names: to module file (or directory for a qualifier). |
| * Other identifiers: to most recent definition before this reference. |
| * This implementation is cached. |
| * |
| * @see #resolveInner(). |
| */ |
| @Override |
| @Nullable |
| public PsiElement resolve() { |
| final ResolveResult[] results = multiResolve(false); |
| return results.length >= 1 && !(results[0] instanceof ImplicitResolveResult) ? results[0].getElement() : null; |
| } |
| |
| // it is *not* final so that it can be changed in debug time. if set to false, caching is off |
| @SuppressWarnings("FieldCanBeLocal") |
| private static boolean USE_CACHE = true; |
| |
| /** |
| * Resolves reference to possible referred elements. |
| * First element is always what resolve() would return. |
| * Imported module names: to module file, or {directory, '__init__.py}' for a qualifier. |
| * todo Local identifiers: a list of definitions in the most recent compound statement |
| * (e.g. <code>if X: a = 1; else: a = 2</code> has two definitions of <code>a</code>.). |
| * todo Identifiers not found locally: similar definitions in imported files and builtins. |
| * |
| * @see com.intellij.psi.PsiPolyVariantReference#multiResolve(boolean) |
| */ |
| @Override |
| @NotNull |
| public ResolveResult[] multiResolve(final boolean incompleteCode) { |
| if (USE_CACHE) { |
| final ResolveCache cache = ResolveCache.getInstance(getElement().getProject()); |
| return cache.resolveWithCaching(this, CachingResolver.INSTANCE, false, incompleteCode); |
| } |
| else { |
| return multiResolveInner(); |
| } |
| } |
| |
| // sorts and modifies results of resolveInner |
| |
| @NotNull |
| private ResolveResult[] multiResolveInner() { |
| final String referencedName = myElement.getReferencedName(); |
| if (referencedName == null) return ResolveResult.EMPTY_ARRAY; |
| |
| List<RatedResolveResult> targets = resolveInner(); |
| if (targets.size() == 0) return ResolveResult.EMPTY_ARRAY; |
| |
| // change class results to constructor results if there are any |
| if (myElement.getParent() instanceof PyCallExpression) { // we're a call |
| ListIterator<RatedResolveResult> it = targets.listIterator(); |
| while (it.hasNext()) { |
| final RatedResolveResult rrr = it.next(); |
| final PsiElement elt = rrr.getElement(); |
| if (elt instanceof PyClass) { |
| PyClass cls = (PyClass)elt; |
| PyFunction init = cls.findMethodByName(PyNames.INIT, false); |
| if (init != null) { |
| // replace |
| it.set(rrr.replace(init)); |
| } |
| else { // init not found; maybe it's ancestor's |
| for (PyClass ancestor : cls.getAncestorClasses(myContext.getTypeEvalContext())) { |
| init = ancestor.findMethodByName(PyNames.INIT, false); |
| if (init != null) { |
| // add to results as low priority |
| it.add(new RatedResolveResult(RatedResolveResult.RATE_LOW, init)); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // put everything in a sorting container |
| List<RatedResolveResult> ret = RatedResolveResult.sorted(targets); |
| return ret.toArray(new ResolveResult[ret.size()]); |
| } |
| |
| @NotNull |
| private static ResolveResultList resolveToLatestDefs(@NotNull List<ReadWriteInstruction> instructions, @NotNull PsiElement element, @NotNull String name) { |
| final ResolveResultList ret = new ResolveResultList(); |
| for (ReadWriteInstruction instruction : instructions) { |
| PsiElement definition = instruction.getElement(); |
| NameDefiner definer = null; |
| // TODO: This check may slow down resolving, but it is the current solution to the comprehension scopes problem |
| if (isInnerComprehension(element, definition)) continue; |
| if (definition instanceof NameDefiner && !(definition instanceof PsiNamedElement)) { |
| definer = (NameDefiner)definition; |
| definition = definer.getElementNamed(name); |
| } |
| if (definer != null) { |
| if (definer instanceof PyImportElement || definer instanceof PyStarImportElement || definer instanceof PyImportedModule) { |
| ret.add(new ImportedResolveResult(definition, getRate(definition), Collections.<PsiElement>singletonList(definer))); |
| } |
| else { |
| ret.poke(definition, getRate(definition)); |
| } |
| // TODO this kind of resolve contract is quite stupid |
| if (definition != null) { |
| ret.poke(definer, RatedResolveResult.RATE_LOW); |
| } |
| } |
| else { |
| ret.poke(definition, getRate(definition)); |
| } |
| } |
| final ResolveResultList results = new ResolveResultList(); |
| for (RatedResolveResult r : ret) { |
| final PsiElement e = r.getElement(); |
| if (e == element) { |
| continue; |
| } |
| if (element instanceof PyTargetExpression && PyPsiUtils.isBefore(element, e)) { |
| continue; |
| } |
| else { |
| results.add(r); |
| } |
| } |
| |
| return results; |
| } |
| |
| private static boolean isInnerComprehension(PsiElement referenceElement, PsiElement definition) { |
| final PyComprehensionElement definitionComprehension = PsiTreeUtil.getParentOfType(definition, PyComprehensionElement.class); |
| if (definitionComprehension != null && PyUtil.isOwnScopeComprehension(definitionComprehension)) { |
| final PyComprehensionElement elementComprehension = PsiTreeUtil.getParentOfType(referenceElement, PyComprehensionElement.class); |
| if (elementComprehension == null || !PsiTreeUtil.isAncestor(definitionComprehension, elementComprehension, false)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isInOwnScopeComprehension(PsiElement uexpr) { |
| PyComprehensionElement comprehensionElement = PsiTreeUtil.getParentOfType(uexpr, PyComprehensionElement.class); |
| return comprehensionElement != null && PyUtil.isOwnScopeComprehension(comprehensionElement); |
| } |
| |
| /** |
| * Does actual resolution of resolve(). |
| * |
| * @return resolution result. |
| * @see #resolve() |
| */ |
| @NotNull |
| protected List<RatedResolveResult> resolveInner() { |
| final ResolveResultList ret = new ResolveResultList(); |
| |
| final String referencedName = myElement.getReferencedName(); |
| if (referencedName == null) return ret; |
| |
| if (myElement instanceof PyTargetExpression) { |
| if (PsiTreeUtil.getParentOfType(myElement, PyComprehensionElement.class) != null) { |
| ret.poke(myElement, getRate(myElement)); |
| return ret; |
| } |
| } |
| |
| // here we have an unqualified expr. it may be defined: |
| // ...in current file |
| ResolveProcessor processor = new ResolveProcessor(referencedName); |
| |
| // Use real context here to enable correct completion and resolve in case of PyExpressionCodeFragment |
| final PsiElement realContext = PyPsiUtils.getRealContext(myElement); |
| |
| PsiElement roof = findResolveRoof(referencedName, realContext); |
| PyResolveUtil.scopeCrawlUp(processor, myElement, referencedName, roof); |
| return getResultsFromProcessor(referencedName, processor, realContext, roof); |
| } |
| |
| protected List<RatedResolveResult> getResultsFromProcessor(String referencedName, |
| ResolveProcessor processor, |
| PsiElement realContext, PsiElement roof) { |
| ResolveResultList ret = new ResolveResultList(); |
| PsiElement uexpr = processor.getResult(); |
| List<PsiElement> definers = processor.getDefiners(); |
| if (uexpr != null) { |
| if (definers.isEmpty()) { |
| final ScopeOwner originalOwner = ScopeUtil.getScopeOwner(realContext); |
| final ScopeOwner owner = ScopeUtil.getScopeOwner(uexpr); |
| if (owner != null) { |
| final Scope scope = ControlFlowCache.getScope(owner); |
| if (uexpr == originalOwner && originalOwner instanceof PyClass) { |
| uexpr = null; |
| } |
| else if (owner == originalOwner && !scope.isGlobal(referencedName)) { |
| final List<ReadWriteInstruction> instructions = PyDefUseUtil.getLatestDefs(owner, referencedName, myElement, false); |
| final ResolveResultList latest = resolveToLatestDefs(instructions, myElement, referencedName); |
| if (!latest.isEmpty()) { |
| return latest; |
| } |
| if (owner instanceof PyClass || (instructions.isEmpty() && isInOwnScopeComprehension(uexpr))) { |
| final ScopeOwner parentOwner = ScopeUtil.getScopeOwner(owner); |
| if (parentOwner != null) { |
| processor = new ResolveProcessor(referencedName); |
| PyResolveUtil.scopeCrawlUp(processor, parentOwner, referencedName, roof); |
| uexpr = processor.getResult(); |
| definers = processor.getDefiners(); |
| } |
| } |
| else { |
| uexpr = null; |
| } |
| } |
| else if (owner != originalOwner && originalOwner != null && !scope.isGlobal(referencedName)) { |
| final Scope originalScope = ControlFlowCache.getScope(originalOwner); |
| if (originalScope.containsDeclaration(referencedName)) { |
| uexpr = null; |
| } |
| } |
| } |
| } |
| // sort what we got |
| for (PsiElement hit : definers) { |
| ret.poke(hit, getRate(hit)); |
| } |
| final PsiElement packageInit = PyUtil.turnDirIntoInit(uexpr); |
| if (packageInit != null) { |
| uexpr = packageInit; // an import statement may have returned a dir |
| } |
| } |
| else if (!definers.isEmpty()) { |
| ret.add(new ImportedResolveResult(null, RatedResolveResult.RATE_LOW, definers)); |
| } |
| PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(realContext); |
| if (uexpr == null) { |
| // ...as a part of current module |
| String name = myElement.getName(); |
| if (PyModuleType.MODULE_MEMBERS.contains(name)) { |
| PyType objectType = builtinCache.getObjectType(); // "object" as a closest kin to "module" |
| if (objectType != null && name != null) { |
| ret.addAll(objectType.resolveMember(name, null, AccessDirection.READ, myContext)); |
| } |
| } |
| } |
| if (uexpr != null) { |
| ret.add(new ImportedResolveResult(uexpr, getRate(uexpr), definers)); |
| } |
| else { |
| for (PyReferenceResolveProvider provider : Extensions.getExtensions(PyReferenceResolveProvider.EP_NAME)) { |
| final List<RatedResolveResult> results = provider.resolveName(myElement, definers); |
| for (RatedResolveResult res : results) { |
| ret.add(res); |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| private PsiElement findResolveRoof(String referencedName, PsiElement realContext) { |
| if (PyUtil.isClassPrivateName(referencedName)) { |
| // a class-private name; limited by either class or this file |
| PsiElement one = myElement; |
| do { |
| one = PyUtil.getConcealingParent(one); |
| } |
| while (one instanceof PyFunction); |
| if (one instanceof PyClass) { |
| PyArgumentList superClassExpressionList = ((PyClass)one).getSuperClassExpressionList(); |
| if (superClassExpressionList == null || !PsiTreeUtil.isAncestor(superClassExpressionList, myElement, false)) { |
| return one; |
| } |
| } |
| } |
| |
| if (myElement instanceof PyTargetExpression) { |
| final ScopeOwner scopeOwner = PsiTreeUtil.getParentOfType(myElement, ScopeOwner.class); |
| final Scope scope; |
| if (scopeOwner != null) { |
| scope = ControlFlowCache.getScope(scopeOwner); |
| final String name = myElement.getName(); |
| if (scope.isNonlocal(name)) { |
| final ScopeOwner nonlocalOwner = ScopeUtil.getDeclarationScopeOwner(myElement, referencedName); |
| if (nonlocalOwner != null && !(nonlocalOwner instanceof PyFile)) { |
| return nonlocalOwner; |
| } |
| } |
| if (!scope.isGlobal(name)) { |
| return scopeOwner; |
| } |
| } |
| } |
| return realContext.getContainingFile(); |
| } |
| |
| // NOTE: very crude |
| |
| public static int getRate(PsiElement elt) { |
| int rate; |
| if (elt instanceof PyImportedNameDefiner || elt instanceof PyReferenceExpression) { |
| rate = RatedResolveResult.RATE_LOW; |
| } |
| else if (elt instanceof PyFile) { |
| rate = RatedResolveResult.RATE_HIGH; |
| } |
| else { |
| rate = RatedResolveResult.RATE_NORMAL; |
| } |
| return rate; |
| } |
| |
| @Override |
| @NotNull |
| public String getCanonicalText() { |
| return getRangeInElement().substring(getElement().getText()); |
| } |
| |
| @Override |
| public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { |
| ASTNode nameElement = myElement.getNameElement(); |
| if (newElementName.endsWith(PyNames.DOT_PY)) { |
| newElementName = newElementName.substring(0, newElementName.length() - PyNames.DOT_PY.length()); |
| } |
| if (nameElement != null && PyNames.isIdentifier(newElementName)) { |
| final ASTNode newNameElement = PyUtil.createNewName(myElement, newElementName); |
| myElement.getNode().replaceChild(nameElement, newNameElement); |
| } |
| return myElement; |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException { |
| return null; |
| } |
| |
| @Override |
| public boolean isReferenceTo(PsiElement element) { |
| if (element instanceof PsiFileSystemItem) { |
| // may be import via alias, so don't check if names match, do simple resolve check instead |
| PsiElement resolveResult = resolve(); |
| if (resolveResult instanceof PyImportedModule) { |
| resolveResult = resolveResult.getNavigationElement(); |
| } |
| if (element instanceof PsiDirectory) { |
| if (resolveResult instanceof PyFile) { |
| final PyFile file = (PyFile)resolveResult; |
| if (PyUtil.isPackage(file) && file.getContainingDirectory() == element) { |
| return true; |
| } |
| } |
| else if (resolveResult instanceof PsiDirectory) { |
| final PsiDirectory directory = (PsiDirectory)resolveResult; |
| if (PyUtil.isPackage(directory, null) && directory == element) { |
| return true; |
| } |
| } |
| } |
| return resolveResult == element; |
| } |
| if (element instanceof PsiNamedElement) { |
| final String elementName = ((PsiNamedElement)element).getName(); |
| if ((Comparing.equal(myElement.getReferencedName(), elementName) || PyNames.INIT.equals(elementName))) { |
| if (!haveQualifiers(element)) { |
| final ScopeOwner ourScopeOwner = ScopeUtil.getScopeOwner(getElement()); |
| final ScopeOwner theirScopeOwner = ScopeUtil.getScopeOwner(element); |
| if (element instanceof PyParameter || element instanceof PyTargetExpression) { |
| // Check if the reference is in the same or inner scope of the element scope, not shadowed by an intermediate declaration |
| if (resolvesToSameLocal(element, elementName, ourScopeOwner, theirScopeOwner)) { |
| return true; |
| } |
| } |
| |
| final PsiElement resolveResult = resolve(); |
| if (resolveResult == element) { |
| return true; |
| } |
| |
| // we shadow their name or they shadow ours (PY-6241) |
| if (resolveResult instanceof PsiNamedElement && resolveResult instanceof ScopeOwner && element instanceof ScopeOwner && |
| theirScopeOwner == ScopeUtil.getScopeOwner(resolveResult)) { |
| return true; |
| } |
| |
| if (!haveQualifiers(element) && ourScopeOwner != null && theirScopeOwner != null) { |
| if (resolvesToSameGlobal(element, elementName, ourScopeOwner, theirScopeOwner, resolveResult)) return true; |
| } |
| |
| if (resolvesToWrapper(element, resolveResult)) { |
| return true; |
| } |
| } |
| if (element instanceof PyExpression) { |
| final PyExpression expr = (PyExpression)element; |
| if (PyUtil.isClassAttribute(myElement) && (PyUtil.isClassAttribute(expr) || PyUtil.isInstanceAttribute(expr))) { |
| final PyClass c1 = PsiTreeUtil.getParentOfType(element, PyClass.class); |
| final PyClass c2 = PsiTreeUtil.getParentOfType(myElement, PyClass.class); |
| if (c1 != null && c2 != null && (c1.isSubclass(c2) || c2.isSubclass(c1))) { |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| private boolean resolvesToSameLocal(PsiElement element, String elementName, ScopeOwner ourScopeOwner, ScopeOwner theirScopeOwner) { |
| final PsiElement ourContainer = findContainer(getElement()); |
| final PsiElement theirContainer = findContainer(element); |
| if (ourContainer != null) { |
| if (ourContainer == theirContainer) { |
| return true; |
| } |
| if (PsiTreeUtil.isAncestor(theirContainer, ourContainer, true)) { |
| if (ourScopeOwner != theirScopeOwner) { |
| boolean shadowsName = false; |
| ScopeOwner owner = ourScopeOwner; |
| while(owner != theirScopeOwner && owner != null) { |
| if (ControlFlowCache.getScope(owner).containsDeclaration(elementName)) { |
| shadowsName = true; |
| break; |
| } |
| owner = ScopeUtil.getScopeOwner(owner); |
| } |
| if (!shadowsName) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Nullable |
| private static PsiElement findContainer(@NotNull PsiElement element) { |
| final PyElement parent = PsiTreeUtil.getParentOfType(element, ScopeOwner.class, PyComprehensionElement.class); |
| if (parent instanceof PyListCompExpression && LanguageLevel.forElement(element).isOlderThan(LanguageLevel.PYTHON30)) { |
| return findContainer(parent); |
| } |
| return parent; |
| } |
| |
| private boolean resolvesToSameGlobal(PsiElement element, String elementName, ScopeOwner ourScopeOwner, ScopeOwner theirScopeOwner, |
| PsiElement resolveResult) { |
| // Handle situations when there is no top-level declaration for globals and transitive resolve doesn't help |
| final PsiFile ourFile = getElement().getContainingFile(); |
| final PsiFile theirFile = element.getContainingFile(); |
| if (ourFile == theirFile) { |
| final boolean ourIsGlobal = ControlFlowCache.getScope(ourScopeOwner).isGlobal(elementName); |
| final boolean theirIsGlobal = ControlFlowCache.getScope(theirScopeOwner).isGlobal(elementName); |
| if (ourIsGlobal && theirIsGlobal) { |
| return true; |
| } |
| } |
| if (ScopeUtil.getScopeOwner(resolveResult) == ourFile && ControlFlowCache.getScope(theirScopeOwner).isGlobal(elementName)) { |
| return true; |
| } |
| return false; |
| } |
| |
| protected boolean resolvesToWrapper(PsiElement element, PsiElement resolveResult) { |
| if (element instanceof PyFunction && ((PyFunction) element).getContainingClass() != null && resolveResult instanceof PyTargetExpression) { |
| final PyExpression assignedValue = ((PyTargetExpression)resolveResult).findAssignedValue(); |
| if (assignedValue instanceof PyCallExpression) { |
| final PyCallExpression call = (PyCallExpression)assignedValue; |
| final Pair<String,PyFunction> functionPair = PyCallExpressionHelper.interpretAsModifierWrappingCall(call, myElement); |
| if (functionPair != null && functionPair.second == element) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private boolean haveQualifiers(PsiElement element) { |
| if (myElement.isQualified()) { |
| return true; |
| } |
| if (element instanceof PyQualifiedExpression && ((PyQualifiedExpression)element).isQualified()) { |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| @NotNull |
| public Object[] getVariants() { |
| final List<LookupElement> ret = Lists.newArrayList(); |
| |
| // Use real context here to enable correct completion and resolve in case of PyExpressionCodeFragment!!! |
| final PsiElement originalElement = CompletionUtil.getOriginalElement(myElement); |
| final PyQualifiedExpression element = originalElement instanceof PyQualifiedExpression ? |
| (PyQualifiedExpression)originalElement : myElement; |
| final PsiElement realContext = PyPsiUtils.getRealContext(element); |
| |
| // include our own names |
| final int underscores = PyUtil.getInitialUnderscores(element.getName()); |
| final CompletionVariantsProcessor processor = new CompletionVariantsProcessor(element); |
| final ScopeOwner owner = realContext instanceof ScopeOwner ? (ScopeOwner)realContext : ScopeUtil.getScopeOwner(realContext); |
| if (owner != null) { |
| PyResolveUtil.scopeCrawlUp(processor, owner, null, null); |
| } |
| |
| // in a call, include function's arg names |
| KeywordArgumentCompletionUtil.collectFunctionArgNames(element, ret); |
| |
| // include builtin names |
| final PyFile builtinsFile = PyBuiltinCache.getInstance(element).getBuiltinsFile(); |
| if (builtinsFile != null) { |
| PyResolveUtil.scopeCrawlUp(processor, builtinsFile, null, null); |
| } |
| |
| if (underscores >= 2) { |
| // if we're a normal module, add module's attrs |
| PsiFile f = realContext.getContainingFile(); |
| if (f instanceof PyFile) { |
| for (String name : PyModuleType.getPossibleInstanceMembers()) { |
| ret.add(LookupElementBuilder.create(name).withIcon(PlatformIcons.FIELD_ICON)); |
| } |
| } |
| } |
| |
| // Throw away fake elements used for completion internally |
| for (LookupElement e : processor.getResultList()) { |
| final Object o = e.getObject(); |
| if (o instanceof PsiElement) { |
| final PsiElement original = CompletionUtil.getOriginalElement((PsiElement)o); |
| if (original == null) { |
| continue; |
| } |
| } |
| ret.add(e); |
| } |
| |
| return ret.toArray(); |
| } |
| |
| @Override |
| public boolean isSoft() { |
| return false; |
| } |
| |
| @Override |
| public HighlightSeverity getUnresolvedHighlightSeverity(TypeEvalContext context) { |
| if (isBuiltInConstant()) return null; |
| final PyExpression qualifier = myElement.getQualifier(); |
| if (qualifier == null) { |
| return HighlightSeverity.ERROR; |
| } |
| if (context.getType(qualifier) != null) { |
| return HighlightSeverity.WARNING; |
| } |
| return null; |
| } |
| |
| private boolean isBuiltInConstant() { |
| // TODO: generalize |
| String name = myElement.getReferencedName(); |
| return PyNames.NONE.equals(name) || "True".equals(name) || "False".equals(name); |
| } |
| |
| @Override |
| @Nullable |
| public String getUnresolvedDescription() { |
| return null; |
| } |
| |
| |
| // our very own caching resolver |
| |
| private static class CachingResolver implements ResolveCache.PolyVariantResolver<PyReferenceImpl> { |
| public static CachingResolver INSTANCE = new CachingResolver(); |
| private ThreadLocal<AtomicInteger> myNesting = new ThreadLocal<AtomicInteger>() { |
| @Override |
| protected AtomicInteger initialValue() { |
| return new AtomicInteger(); |
| } |
| }; |
| |
| private static final int MAX_NESTING_LEVEL = 30; |
| |
| @Override |
| @NotNull |
| public ResolveResult[] resolve(@NotNull final PyReferenceImpl ref, final boolean incompleteCode) { |
| if (myNesting.get().getAndIncrement() >= MAX_NESTING_LEVEL) { |
| System.out.println("Stack overflow pending"); |
| } |
| try { |
| return ref.multiResolveInner(); |
| } |
| finally { |
| myNesting.get().getAndDecrement(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| PyReferenceImpl that = (PyReferenceImpl)o; |
| |
| if (!myElement.equals(that.myElement)) return false; |
| if (!myContext.equals(that.myContext)) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| return myElement.hashCode(); |
| } |
| |
| protected static Object[] getTypeCompletionVariants(PyExpression pyExpression, PyType type) { |
| ProcessingContext context = new ProcessingContext(); |
| context.put(PyType.CTX_NAMES, new HashSet<String>()); |
| return type.getCompletionVariants(pyExpression.getName(), pyExpression, context); |
| } |
| } |