From 359b13a62f12561aeb7c3faa377242994b1b53f2 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 20 Jan 2016 13:14:26 +0300 Subject: [PATCH] Resolve PyReferenceImpl refs to all outer definitions in the current file This applies to all outer definitions except imports where we resolve only to the latest imports as we used to do. This is required for detecting and optimizing unused imports and adding imports during refactorings. The implementation of ResolveProcessor has been rewritten as PyResolveProcessor. --- .../plugins/ipnb/psi/IpnbPyReference.java | 2 +- .../controlflow/PyControlFlowBuilder.java | 17 +- .../controlflow/ReadWriteInstruction.java | 7 +- .../codeInsight/dataflow/scope/Scope.java | 4 +- .../dataflow/scope/impl/ScopeImpl.java | 41 ++-- .../DeclarationConflictChecker.java | 32 +-- .../ImportFromToImportIntention.java | 11 +- .../ImportToggleAliasIntention.java | 3 +- .../documentation/doctest/PyDocReference.java | 6 +- .../PyShadowingNamesInspection.java | 28 +-- .../python/psi/impl/PropertyBunch.java | 12 +- .../psi/impl/references/PyReferenceImpl.java | 198 ++++++++++-------- .../psi/resolve/PyResolveProcessor.java | 173 +++++++++++++++ .../python/psi/resolve/PyResolveUtil.java | 72 ++----- ...PythonBuiltinReferenceResolveProvider.java | 7 +- .../python/psi/resolve/ResolveProcessor.java | 194 ----------------- .../python/psi/types/PyClassTypeImpl.java | 7 +- .../psi/types/PyImportedModuleType.java | 4 +- .../python/refactoring/PyDefUseUtil.java | 7 + .../a.py | 2 +- .../unusedPackageAndSubmodule/test1.py | 2 +- .../alphabeticalOrder.after.py | 1 - .../refactoring/move/module/after/src/a.py | 1 - .../resolve/GlobalNotDefinedAtTopLevel.py | 4 + .../com/jetbrains/python/PyResolveTest.java | 4 + 25 files changed, 419 insertions(+), 420 deletions(-) create mode 100644 python/src/com/jetbrains/python/psi/resolve/PyResolveProcessor.java delete mode 100644 python/src/com/jetbrains/python/psi/resolve/ResolveProcessor.java create mode 100644 python/testData/resolve/GlobalNotDefinedAtTopLevel.py diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/psi/IpnbPyReference.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/psi/IpnbPyReference.java index 6188dcc88f3e..94b60ac6c50c 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/psi/IpnbPyReference.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/psi/IpnbPyReference.java @@ -69,7 +69,7 @@ public class IpnbPyReference extends PyReferenceImpl { final Editor editor = ((IpnbCodePanel)editablePanel).getEditor(); final IpnbPyFragment psiFile = (IpnbPyFragment)PsiDocumentManager.getInstance(myElement.getProject()).getPsiFile(editor.getDocument()); if (psiFile == null) continue; - ResolveProcessor processor = new ResolveProcessor(referencedName); + final PyResolveProcessor processor = new PyResolveProcessor(referencedName); PyResolveUtil.scopeCrawlUp(processor, psiFile, referencedName, psiFile); final List resultList = getResultsFromProcessor(referencedName, processor, psiFile, psiFile); diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index fe3c86e35ff6..143936d53912 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -223,16 +223,25 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { @Override public void visitPyFromImportStatement(PyFromImportStatement node) { visitPyImportStatementBase(node); + final PyStarImportElement starImportElement = node.getStarImportElement(); + if (starImportElement != null) { + starImportElement.accept(this); + } + } + + @Override + public void visitPyStarImportElement(PyStarImportElement node) { + final ReadWriteInstruction instruction = ReadWriteInstruction.importStar(myBuilder, node); + myBuilder.addNode(instruction); + myBuilder.checkPending(instruction); } private void visitPyImportStatementBase(PyImportStatementBase node) { myBuilder.startNode(node); for (PyImportElement importElement : node.getImportElements()) { final ReadWriteInstruction instruction = ReadWriteInstruction.write(myBuilder, importElement, importElement.getVisibleName()); - if (instruction != null) { - myBuilder.addNode(instruction); - myBuilder.checkPending(instruction); - } + myBuilder.addNode(instruction); + myBuilder.checkPending(instruction); } } diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/ReadWriteInstruction.java b/python/src/com/jetbrains/python/codeInsight/controlflow/ReadWriteInstruction.java index e7c2d05a2bea..2f3e7e3dcf9f 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/ReadWriteInstruction.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/ReadWriteInstruction.java @@ -38,7 +38,8 @@ public class ReadWriteInstruction extends InstructionImpl { READ(true, false, false), WRITE(false, true, false), ASSERTTYPE(false, false, true), - READWRITE(true, true, false); + READWRITE(true, true, false), + IMPORT_STAR(false, false, false); private final boolean isWrite; private final boolean isRead; @@ -119,6 +120,10 @@ public class ReadWriteInstruction extends InstructionImpl { return new ReadWriteInstruction(builder, element, name, ACCESS.ASSERTTYPE, getType); } + public static ReadWriteInstruction importStar(ControlFlowBuilder builder, PsiElement element) { + return new ReadWriteInstruction(builder, element, null, ACCESS.IMPORT_STAR); + } + @Nullable public PyType getType(TypeEvalContext context, @Nullable PsiElement anchor) { return myGetType.getType(context, anchor); diff --git a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java index 2467f1235098..207688fb53cd 100644 --- a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java +++ b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/Scope.java @@ -45,8 +45,8 @@ public interface Scope { @NotNull List getImportedNameDefiners(); - @Nullable - PsiNamedElement getNamedElement(String name, boolean includeNestedGlobals); + @NotNull + Collection getNamedElements(String name, boolean includeNestedGlobals); @NotNull Collection getNamedElements(); diff --git a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java index 92b99eb8c502..732d45bf6d71 100644 --- a/python/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java +++ b/python/src/com/jetbrains/python/codeInsight/dataflow/scope/impl/ScopeImpl.java @@ -15,6 +15,8 @@ */ package com.jetbrains.python.codeInsight.dataflow.scope.impl; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.intellij.codeInsight.controlflow.Instruction; import com.intellij.codeInsight.dataflow.DFALimitExceededException; import com.intellij.codeInsight.dataflow.map.DFAMap; @@ -30,7 +32,6 @@ import com.jetbrains.python.codeInsight.dataflow.scope.ScopeVariable; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.*; @@ -44,7 +45,7 @@ public class ScopeImpl implements Scope { private volatile Set myNonlocals; private volatile List myNestedScopes; private final ScopeOwner myFlowOwner; - private volatile Map myNamedElements; + private volatile Map> myNamedElements; private volatile List myImportedNameDefiners; // Declarations which declare unknown set of imported names private volatile Set myAugAssignments; private List myTargetExpressions; @@ -118,7 +119,7 @@ public class ScopeImpl implements Scope { if (isNonlocal(name)) { return false; } - if (getNamedElement(name, true) != null) { + if (!getNamedElements(name, true).isEmpty()) { return true; } if (isAugAssignment(name)) { @@ -141,25 +142,24 @@ public class ScopeImpl implements Scope { return myImportedNameDefiners; } - @Nullable + @NotNull @Override - public PsiNamedElement getNamedElement(String name, boolean includeNestedGlobals) { + public Collection getNamedElements(String name, boolean includeNestedGlobals) { if (myNamedElements == null) { collectDeclarations(); } - final PsiNamedElement element = myNamedElements.get(name); - if (element != null) { - return element; + if (myNamedElements.containsKey(name)) { + return myNamedElements.get(name); } if (includeNestedGlobals && isGlobal(name)) { for (Scope scope : myNestedScopes) { - final PsiNamedElement global = scope.getNamedElement(name, true); - if (global != null) { - return global; + final Collection globals = scope.getNamedElements(name, true); + if (!globals.isEmpty()) { + return globals; } } } - return null; + return Collections.emptyList(); } @NotNull @@ -168,7 +168,11 @@ public class ScopeImpl implements Scope { if (myNamedElements == null) { collectDeclarations(); } - return myNamedElements.values(); + final List results = Lists.newArrayList(); + for (Collection elements : myNamedElements.values()) { + results.addAll(elements); + } + return results; } @NotNull @@ -181,7 +185,7 @@ public class ScopeImpl implements Scope { } private void collectDeclarations() { - final Map namedElements = new HashMap(); + final Map> namedElements = new HashMap>(); final List importedNameDefiners = new ArrayList(); final List nestedScopes = new ArrayList(); final Set globals = new HashSet(); @@ -211,7 +215,6 @@ public class ScopeImpl implements Scope { for (PyTargetExpression expression : node.getGlobals()) { final String name = expression.getReferencedName(); globals.add(name); - namedElements.put(name, expression); } super.visitPyGlobalStatement(node); } @@ -238,7 +241,11 @@ public class ScopeImpl implements Scope { @Override public void visitPyElement(PyElement node) { if (node instanceof PsiNamedElement && !(node instanceof PyKeywordArgument)) { - namedElements.put(node.getName(), (PsiNamedElement)node); + final String name = node.getName(); + if (!namedElements.containsKey(name)) { + namedElements.put(name, Sets.newLinkedHashSet()); + } + namedElements.get(name).add((PsiNamedElement)node); } if (node instanceof PyImportedNameDefiner) { importedNameDefiners.add((PyImportedNameDefiner)node); @@ -253,8 +260,6 @@ public class ScopeImpl implements Scope { } }); - Collections.reverse(importedNameDefiners); - myNamedElements = namedElements; myImportedNameDefiners = importedNameDefiners; myNestedScopes = nestedScopes; diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java b/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java index cac318dc1a51..4b75dd59f1cd 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java @@ -24,14 +24,17 @@ import com.intellij.usages.UsageTarget; import com.intellij.usages.UsageViewManager; import com.intellij.usages.UsageViewPresentation; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.psi.resolve.PyResolveProcessor; import com.jetbrains.python.psi.resolve.PyResolveUtil; -import com.jetbrains.python.psi.resolve.ResolveProcessor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; /** * An utility class that checks local definitions of a given name and can show a conflicts panel. @@ -50,24 +53,21 @@ public class DeclarationConflictChecker { * @return a list of pairs (referring element, element that defines name). */ @NotNull - public static List> findDefinitions( - String name, Collection references, @Nullable Collection ignored - ) { + public static List> findDefinitions(@NotNull String name, + @NotNull Collection references, + @NotNull Set ignored) { List> conflicts = new ArrayList>(); - REF_LOOP: for (PsiReference ref : references) { - ResolveProcessor processor = new ResolveProcessor(name); - PyResolveUtil.treeCrawlUp(processor, ref.getElement()); - PsiElement result = processor.getResult(); - if (result != null) { - List definers = processor.getDefiners(); - if (definers != null && definers.size() > 0) { - result = definers.get(0); // in this case, processor's result is one hop of resolution too far from what we want. + final PsiElement refElement = ref.getElement(); + final ScopeOwner owner = ScopeUtil.getScopeOwner(refElement); + final PyResolveProcessor processor = new PyResolveProcessor(name, true); + if (owner != null) { + PyResolveUtil.scopeCrawlUp(processor, owner, name, null); + for (PsiElement element : processor.getElements()) { + if (!ignored.contains(element)) { + conflicts.add(Pair.create(refElement, element)); + } } - if (ignored != null) for (PsiElement ignorable : ignored) { - if (result == ignorable) continue REF_LOOP; - } - conflicts.add(Pair.create(ref.getElement(), result)); } } return conflicts; diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java index d0eda3752e99..a65b67fa0c61 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.codeInsight.intentions; +import com.google.common.collect.Sets; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.lang.ASTNode; import com.intellij.openapi.editor.Document; @@ -242,13 +243,9 @@ public class ImportFromToImportIntention implements IntentionAction { possible_targets.addAll(references.keySet()); possible_targets.addAll(star_references); } - if ( - showConflicts( - project, - findDefinitions(top_name, possible_targets, Arrays.asList(info.myFromImportStatement.getImportElements())), - top_name, info.myModuleName - ) - ) { + final Set ignored = Sets.newHashSet(Arrays.asList(info.myFromImportStatement.getImportElements())); + if (top_name != null && showConflicts(project, findDefinitions(top_name, possible_targets, ignored), + top_name, info.myModuleName)) { return; // got conflicts } diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java index d3736c23e58f..e3bbba818f31 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java @@ -43,6 +43,7 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import static com.jetbrains.python.codeInsight.intentions.DeclarationConflictChecker.findDefinitions; @@ -209,7 +210,7 @@ public class ImportToggleAliasIntention implements IntentionAction { } }); // no references here is OK by us. - if (showConflicts(project, findDefinitions(target_name, references, null), target_name, null)) { + if (showConflicts(project, findDefinitions(target_name, references, Collections.emptySet()), target_name, null)) { return; // got conflicts } diff --git a/python/src/com/jetbrains/python/documentation/doctest/PyDocReference.java b/python/src/com/jetbrains/python/documentation/doctest/PyDocReference.java index 2a2b669422f0..dcbdc7f3988e 100644 --- a/python/src/com/jetbrains/python/documentation/doctest/PyDocReference.java +++ b/python/src/com/jetbrains/python/documentation/doctest/PyDocReference.java @@ -64,11 +64,11 @@ public class PyDocReference extends PyReferenceImpl { final List> files = languageManager.getInjectedPsiFiles(host); if (files != null) { for (Pair pair : files) { - ResolveProcessor processor = new ResolveProcessor(referencedName); + final PyResolveProcessor processor = new PyResolveProcessor(referencedName); PyResolveUtil.scopeCrawlUp(processor, (ScopeOwner)pair.getFirst(), referencedName, pair.getFirst()); final List resultList = getResultsFromProcessor(referencedName, processor, pair.getFirst(), - pair.getFirst()); + pair.getFirst()); if (resultList.size() > 0) { List ret = RatedResolveResult.sorted(resultList); return ret.toArray(new RatedResolveResult[ret.size()]); @@ -77,7 +77,7 @@ public class PyDocReference extends PyReferenceImpl { } } - ResolveProcessor processor = new ResolveProcessor(referencedName); + final PyResolveProcessor processor = new PyResolveProcessor(referencedName); if (file instanceof ScopeOwner) PyResolveUtil.scopeCrawlUp(processor, (ScopeOwner)file, referencedName, file); diff --git a/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java b/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java index 494096480f89..795aab4b5a23 100644 --- a/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java @@ -28,8 +28,8 @@ import com.jetbrains.python.codeInsight.dataflow.scope.Scope; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.inspections.quickfix.PyRenameElementQuickFix; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.resolve.PyResolveProcessor; import com.jetbrains.python.psi.resolve.PyResolveUtil; -import com.jetbrains.python.psi.resolve.ResolveProcessor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -98,20 +98,22 @@ public class PyShadowingNamesInspection extends PyInspection { if (owner != null) { final ScopeOwner nextOwner = ScopeUtil.getScopeOwner(owner); if (nextOwner != null) { - final ResolveProcessor processor = new ResolveProcessor(name); - PyResolveUtil.scopeCrawlUp(processor, nextOwner, null, name, null, null); - final PsiElement resolved = processor.getResult(); - if (resolved != null) { - final PyComprehensionElement comprehension = PsiTreeUtil.getParentOfType(resolved, PyComprehensionElement.class); - if (comprehension != null && PyUtil.isOwnScopeComprehension(comprehension)) { + final PyResolveProcessor processor = new PyResolveProcessor(name); + PyResolveUtil.scopeCrawlUp(processor, nextOwner, null, name, null); + for (PsiElement resolved : processor.getElements()) { + if (resolved != null) { + final PyComprehensionElement comprehension = PsiTreeUtil.getParentOfType(resolved, PyComprehensionElement.class); + if (comprehension != null && PyUtil.isOwnScopeComprehension(comprehension)) { + return; + } + final Scope scope = ControlFlowCache.getScope(owner); + if (scope.isGlobal(name) || scope.isNonlocal(name)) { + return; + } + registerProblem(problemElement, String.format("Shadows name '%s' from outer scope", name), + ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix()); return; } - final Scope scope = ControlFlowCache.getScope(owner); - if (scope.isGlobal(name) || scope.isNonlocal(name)) { - return; - } - registerProblem(problemElement, String.format("Shadows name '%s' from outer scope", name), - ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix()); } } } diff --git a/python/src/com/jetbrains/python/psi/impl/PropertyBunch.java b/python/src/com/jetbrains/python/psi/impl/PropertyBunch.java index badcdfe0a83b..88c4e65cbf74 100644 --- a/python/src/com/jetbrains/python/psi/impl/PropertyBunch.java +++ b/python/src/com/jetbrains/python/psi/impl/PropertyBunch.java @@ -15,12 +15,13 @@ */ package com.jetbrains.python.psi.impl; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.util.ArrayUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.resolve.PyResolveProcessor; import com.jetbrains.python.psi.resolve.PyResolveUtil; -import com.jetbrains.python.psi.resolve.ResolveProcessor; import com.jetbrains.python.toolbox.Maybe; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -102,10 +103,13 @@ public abstract class PropertyBunch { protected static boolean resolvesLocally(@NotNull PyReferenceExpression ref) { final String name = ref.getName(); if (name != null) { - final ResolveProcessor processor = new ResolveProcessor(name); - processor.setLocalResolve(); + final PyResolveProcessor processor = new PyResolveProcessor(name, true); PyResolveUtil.scopeCrawlUp(processor, ref, name, null); - return processor.getResult() != null; + for (PsiElement element : processor.getElements()) { + if (element != null) { + return true; + } + } } return false; } diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java index bfb5e81e5262..b27ad43ea6d3 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java @@ -48,10 +48,7 @@ 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.*; import java.util.concurrent.atomic.AtomicInteger; /** @@ -105,7 +102,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference * (e.g. if X: a = 1; else: a = 2 has two definitions of a.). * todo Identifiers not found locally: similar definitions in imported files and builtins. * - * @see com.intellij.psi.PsiPolyVariantReference#multiResolve(boolean) + * @see PsiPolyVariantReference#multiResolve(boolean) */ @Override @NotNull @@ -162,7 +159,10 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference } @NotNull - private static ResolveResultList resolveToLatestDefs(@NotNull List instructions, @NotNull PsiElement element, @NotNull String name) { + private static ResolveResultList resolveToLatestDefs(@NotNull List instructions, + @NotNull PsiElement element, + @NotNull String name, + @NotNull TypeEvalContext context) { final ResolveResultList ret = new ResolveResultList(); for (ReadWriteInstruction instruction : instructions) { PsiElement definition = instruction.getElement(); @@ -175,10 +175,10 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference } if (definer != null) { if (definer instanceof PyImportElement || definer instanceof PyStarImportElement || definer instanceof PyImportedModule) { - ret.add(new ImportedResolveResult(definition, getRate(definition), Collections.singletonList(definer))); + ret.add(new ImportedResolveResult(definition, getRate(definition, context), Collections.singletonList(definer))); } else { - ret.poke(definition, getRate(definition)); + ret.poke(definition, getRate(definition, context)); } // TODO this kind of resolve contract is quite stupid if (definition != null) { @@ -186,7 +186,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference } } else { - ret.poke(definition, getRate(definition)); + ret.poke(definition, getRate(definition, context)); } } final ResolveResultList results = new ResolveResultList(); @@ -195,7 +195,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference if (e == element) { continue; } - if (element instanceof PyTargetExpression && PyPsiUtils.isBefore(element, e)) { + if (element instanceof PyTargetExpression && e != null && PyPsiUtils.isBefore(element, e)) { continue; } else { @@ -237,14 +237,14 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference if (myElement instanceof PyTargetExpression) { if (PsiTreeUtil.getParentOfType(myElement, PyComprehensionElement.class) != null) { - ret.poke(myElement, getRate(myElement)); + ret.poke(myElement, getRate(myElement, myContext.getTypeEvalContext())); return ret; } } // here we have an unqualified expr. it may be defined: // ...in current file - ResolveProcessor processor = new ResolveProcessor(referencedName); + final PyResolveProcessor processor = new PyResolveProcessor(referencedName); // Use real context here to enable correct completion and resolve in case of PyExpressionCodeFragment final PsiElement realContext = PyPsiUtils.getRealContext(myElement); @@ -254,84 +254,103 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference return getResultsFromProcessor(referencedName, processor, realContext, roof); } - protected List getResultsFromProcessor(String referencedName, - ResolveProcessor processor, - PsiElement realContext, PsiElement roof) { - ResolveResultList ret = new ResolveResultList(); - PsiElement uexpr = processor.getResult(); - List 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; + protected List getResultsFromProcessor(@NotNull String referencedName, + @NotNull PyResolveProcessor processor, + @Nullable PsiElement realContext, + @Nullable PsiElement resolveRoof) { + boolean unreachableLocalDeclaration = false; + boolean resolveInParentScope = false; + final ResolveResultList resultList = new ResolveResultList(); + final ScopeOwner referenceOwner = ScopeUtil.getScopeOwner(realContext); + final TypeEvalContext typeEvalContext = myContext.getTypeEvalContext(); + ScopeOwner resolvedOwner = processor.getOwner(); + + if (resolvedOwner != null && !processor.isImplicitName() && !processor.getResults().isEmpty()) { + final Collection resolvedElements = processor.getElements(); + final Scope resolvedScope = ControlFlowCache.getScope(resolvedOwner); + + if (!resolvedScope.isGlobal(referencedName)) { + if (resolvedOwner == referenceOwner) { + final List instructions = PyDefUseUtil.getLatestDefs(resolvedOwner, referencedName, realContext, false); + // TODO: Use the results from the processor as a cache for resolving to latest defs + final ResolveResultList latestDefs = resolveToLatestDefs(instructions, realContext, referencedName, typeEvalContext); + if (!latestDefs.isEmpty()) { + return latestDefs; } - else if (owner == originalOwner && !scope.isGlobal(referencedName)) { - final List instructions = PyDefUseUtil.getLatestDefs(owner, referencedName, realContext, false); - final ResolveResultList latest = resolveToLatestDefs(instructions, realContext, 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 (resolvedOwner instanceof PyClass || instructions.isEmpty() && allInOwnScopeComprehensions(resolvedElements)) { + resolveInParentScope = true; } - else if (owner != originalOwner && originalOwner != null && !scope.isGlobal(referencedName)) { - final Scope originalScope = ControlFlowCache.getScope(originalOwner); - if (originalScope.containsDeclaration(referencedName)) { - uexpr = null; - } + else { + unreachableLocalDeclaration = true; } } - } - // 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 results = provider.resolveName(myElement, definers); - for (RatedResolveResult res : results) { - ret.add(res); + else if (referenceOwner != null) { + final Scope referenceScope = ControlFlowCache.getScope(referenceOwner); + if (referenceScope.containsDeclaration(referencedName)) { + unreachableLocalDeclaration = true; + } } } } - return ret; + // TODO: Try resolve to latest defs for outer scopes starting from the last element in CFG (=> no need for a special rate for globals) + + if (!unreachableLocalDeclaration) { + if (resolveInParentScope) { + processor = new PyResolveProcessor(referencedName); + resolvedOwner = ScopeUtil.getScopeOwner(resolvedOwner); + if (resolvedOwner != null) { + PyResolveUtil.scopeCrawlUp(processor, resolvedOwner, referencedName, resolveRoof); + } + } + + for (Map.Entry entry : processor.getResults().entrySet()) { + final PsiElement resolved = entry.getKey(); + final PsiElement definer = entry.getValue(); + if (resolved != null) { + if (typeEvalContext.maySwitchToAST(resolved) && isInnerComprehension(realContext, resolved)) { + continue; + } + if (resolved == referenceOwner && referenceOwner instanceof PyClass) { + continue; + } + if (definer == null) { + resultList.poke(resolved, getRate(resolved, typeEvalContext)); + } + else { + resultList.poke(definer, getRate(definer, typeEvalContext)); + resultList.add(new ImportedResolveResult(resolved, getRate(resolved, typeEvalContext), Collections.singletonList(definer))); + } + } + else if (definer != null) { + resultList.add(new ImportedResolveResult(null, RatedResolveResult.RATE_LOW, Collections.singletonList(definer))); + } + } + + if (!resultList.isEmpty()) { + return resultList; + } + } + + return resolveByReferenceResolveProviders(); + } + + private static boolean allInOwnScopeComprehensions(@NotNull Collection elements) { + for (PsiElement element : elements) { + if (!isInOwnScopeComprehension(element)) { + return false; + } + } + return true; + } + + @NotNull + private ResolveResultList resolveByReferenceResolveProviders() { + final ResolveResultList results = new ResolveResultList(); + for (PyReferenceResolveProvider provider : Extensions.getExtensions(PyReferenceResolveProvider.EP_NAME)) { + results.addAll(provider.resolveName(myElement, Collections.emptyList())); + } + return results; } private PsiElement findResolveRoof(String referencedName, PsiElement realContext) { @@ -370,11 +389,18 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference return realContext.getContainingFile(); } - // NOTE: very crude - - public static int getRate(PsiElement elt) { + public static int getRate(PsiElement elt, @NotNull TypeEvalContext context) { int rate; - if (elt instanceof PyImportedNameDefiner || elt instanceof PyReferenceExpression) { + if (elt instanceof PyTargetExpression && context.maySwitchToAST(elt)) { + final PsiElement parent = elt.getParent(); + if (parent instanceof PyGlobalStatement || parent instanceof PyNonlocalStatement) { + rate = RatedResolveResult.RATE_LOW; + } + else { + rate = RatedResolveResult.RATE_NORMAL; + } + } + else if (elt instanceof PyImportedNameDefiner || elt instanceof PyReferenceExpression) { rate = RatedResolveResult.RATE_LOW; } else if (elt instanceof PyFile) { diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveProcessor.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveProcessor.java new file mode 100644 index 000000000000..2656a74dff3d --- /dev/null +++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveProcessor.java @@ -0,0 +1,173 @@ +/* + * Copyright 2000-2016 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.resolve; + +import com.google.common.collect.Maps; +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.ResolveState; +import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.util.QualifiedName; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; + +/** + * @author vlan + */ +public class PyResolveProcessor implements PsiScopeProcessor { + @NotNull private final String myName; + private final boolean myLocalResolve; + @NotNull private final Map myResults = Maps.newLinkedHashMap(); + @Nullable private ScopeOwner myOwner; + private boolean myImplicitName = false; + + public PyResolveProcessor(@NotNull String name) { + this(name, false); + } + + public PyResolveProcessor(@NotNull String name, boolean localResolve) { + myName = name; + myLocalResolve = localResolve; + } + + @Override + public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { + final PsiNamedElement namedElement = PyUtil.as(element, PsiNamedElement.class); + if (namedElement != null && myName.equals(namedElement.getName())) { + return tryAddResult(element, null); + } + final NameDefiner nameDefiner = PyUtil.as(element, NameDefiner.class); + if (nameDefiner != null) { + final PsiElement resolved = resolveInNameDefiner(nameDefiner); + if (resolved != null) { + return tryAddResult(resolved, nameDefiner); + } + final PyImportElement importElement = PyUtil.as(element, PyImportElement.class); + if (importElement != null) { + if (addResultIfImportMakesNameVisibleInInit(importElement)) { + myImplicitName = true; + } + else { + final String importName = importElement.getVisibleName(); + if (myName.equals(importName)) { + return tryAddResult(null, importElement); + } + } + } + } + return myOwner == null || myOwner == ScopeUtil.getScopeOwner(element); + } + + @Nullable + @Override + public T getHint(@NotNull Key hintKey) { + return null; + } + + @Override + public void handleEvent(@NotNull Event event, @Nullable Object associated) { + } + + @NotNull + public Map getResults() { + return myResults; + } + + @NotNull + public Collection getElements() { + return myResults.keySet(); + } + + @NotNull + public Collection getDefiners() { + return myResults.values(); + } + + @Nullable + public ScopeOwner getOwner() { + return myOwner; + } + + public boolean isImplicitName() { + return myImplicitName; + } + + @Nullable + private PsiElement resolveInNameDefiner(@NotNull NameDefiner definer) { + if (myLocalResolve) { + final PyImportElement importElement = PyUtil.as(definer, PyImportElement.class); + if (importElement != null) { + return importElement.getElementNamed(myName, false); + } + else if (definer instanceof PyStarImportElement) { + return null; + } + } + return definer.getElementNamed(myName); + } + + private boolean tryAddResult(@Nullable PsiElement element, @Nullable PsiElement definer) { + final ScopeOwner owner = ScopeUtil.getScopeOwner(definer != null ? definer : element); + if (myOwner == null) { + myOwner = owner; + } + final boolean sameScope = owner == myOwner; + if (sameScope) { + myResults.put(element, definer != null ? definer : null); + } + return sameScope; + } + + /** + * @see http://stackoverflow.com/questions/6048786/from-module-import-in-init-py-makes-module-name-visible + */ + private boolean addResultIfImportMakesNameVisibleInInit(@NotNull PyImportElement element) { + final QualifiedName importedQName = element.getImportedQName(); + final PsiFile file = element.getContainingFile(); + if (file != null && PyUtil.isPackage(file)) { + if (importedQName != null && importedQName.getComponentCount() > 1 && myName.equals(importedQName.getLastComponent())) { + final QualifiedName packageQName = importedQName.removeLastComponent(); + final PsiElement resolvedImport = PyUtil.turnDirIntoInit(ResolveImportUtil.resolveImportElement(element, packageQName)); + if (resolvedImport == file) { + tryAddResult(element.resolve(), element); + myImplicitName = true; + return true; + } + } + final PyFromImportStatement fromImport = PyUtil.as(element.getContainingImportStatement(), PyFromImportStatement.class); + if (fromImport != null) { + final QualifiedName importSourceQName = fromImport.getImportSourceQName(); + if (importSourceQName != null && importSourceQName.endsWith(myName)) { + final PsiElement resolvedImportSource = PyUtil.turnInitIntoDir(fromImport.resolveImportSource()); + if (resolvedImportSource != null && resolvedImportSource.getParent() == file.getContainingDirectory()) { + tryAddResult(resolvedImportSource, fromImport); + myImplicitName = true; + return true; + } + } + } + } + return false; + } +} diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java index 5c6f93811661..b199e0644c15 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java @@ -38,11 +38,6 @@ import com.jetbrains.python.psi.impl.PyPsiUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - /** * Ref resolution routines. * User: dcheryasov @@ -117,71 +112,39 @@ public class PyResolveUtil { owner = outerScopeOwner; } } - scopeCrawlUp(processor, owner, originalOwner, name, roof, realContext); + scopeCrawlUp(processor, owner, originalOwner, name, roof); } public static void scopeCrawlUp(@NotNull PsiScopeProcessor processor, @NotNull ScopeOwner scopeOwner, @Nullable String name, @Nullable PsiElement roof) { - scopeCrawlUp(processor, scopeOwner, scopeOwner, name, roof, null); + scopeCrawlUp(processor, scopeOwner, scopeOwner, name, roof); } public static void scopeCrawlUp(@NotNull PsiScopeProcessor processor, @Nullable ScopeOwner scopeOwner, - @Nullable ScopeOwner originalScopeOwner, @Nullable String name, @Nullable PsiElement roof, - @Nullable final PsiElement anchor) { + @Nullable ScopeOwner originalScopeOwner, @Nullable String name, @Nullable PsiElement roof) { while (scopeOwner != null) { if (!(scopeOwner instanceof PyClass) || scopeOwner == originalScopeOwner) { final Scope scope = ControlFlowCache.getScope(scopeOwner); - boolean found = false; if (name != null) { final boolean includeNestedGlobals = scopeOwner instanceof PyFile; - final PsiElement resolved = scope.getNamedElement(name, includeNestedGlobals); - if (resolved != null) { + for (PsiNamedElement resolved : scope.getNamedElements(name, includeNestedGlobals)) { if (!processor.execute(resolved, ResolveState.initial())) { - found = true; + return; } } } else { for (PsiNamedElement element : scope.getNamedElements()) { if (!processor.execute(element, ResolveState.initial())) { - found = true; - break; + return; } } } - List definers = new ArrayList(scope.getImportedNameDefiners()); - if (anchor != null && ScopeUtil.getScopeOwner(anchor) == scopeOwner) { - final Comparator nearestDefinerComparator = new Comparator() { - @Override - public int compare(NameDefiner a, NameDefiner b) { - final boolean aIsBefore = PyPsiUtils.isBefore(a, anchor); - final boolean bIsBefore = PyPsiUtils.isBefore(b, anchor); - final int diff = a.getTextOffset() - b.getTextOffset(); - if (aIsBefore && bIsBefore) { - return -diff; - } - else if (aIsBefore) { - return -1; - } - else if (bIsBefore) { - return 1; - } - else { - return diff; - } - } - }; - Collections.sort(definers, nearestDefinerComparator); - } - for (NameDefiner definer : definers) { + for (NameDefiner definer : scope.getImportedNameDefiners()) { if (!processor.execute(definer, ResolveState.initial())) { - found = true; - break; + return; } } - if (found) { - return; - } } if (scopeOwner == roof) { return; @@ -195,14 +158,12 @@ public class PyResolveUtil { * * @param processor a visitor that says when the crawl is done and collects info. * @param elt element from which we start (not checked by processor); if null, the search immediately returns null. - * @return first element that the processor accepted. * * @deprecated Use {@link #scopeCrawlUp} instead. */ @Deprecated - @Nullable - public static PsiElement treeCrawlUp(PsiScopeProcessor processor, PsiElement elt) { - if (elt == null || !elt.isValid()) return null; // can't find anyway. + public static void treeCrawlUp(PsiScopeProcessor processor, PsiElement elt) { + if (elt == null || !elt.isValid()) return; PsiElement seeker = elt; PsiElement cap = PyUtil.getConcealingParent(elt); PyFunction capFunction = cap != null ? PsiTreeUtil.getParentOfType(cap, PyFunction.class, false) : null; @@ -244,27 +205,18 @@ public class PyResolveUtil { // check what we got if (seeker != null) { if (!processor.execute(seeker, ResolveState.initial())) { - if (processor instanceof ResolveProcessor) { - return ((ResolveProcessor)processor).getResult(); - } - else { - return seeker; - } // can't point to exact element, but somewhere here + return; } } } while (seeker != null); - if (processor instanceof ResolveProcessor) { - return ((ResolveProcessor)processor).getResult(); - } - return null; } /** * @param innerFunction a method, presumably inside the class * @param outer an element presumably in the class context. * @return true if an outer element is in a class context, while the inner is a method or function inside it. - * @see com.jetbrains.python.psi.PyUtil#getConcealingParent(com.intellij.psi.PsiElement) + * @see PyUtil#getConcealingParent(PsiElement) */ private static boolean refersFromMethodToClass(final PyFunction innerFunction, final PsiElement outer) { if (innerFunction == null) { diff --git a/python/src/com/jetbrains/python/psi/resolve/PythonBuiltinReferenceResolveProvider.java b/python/src/com/jetbrains/python/psi/resolve/PythonBuiltinReferenceResolveProvider.java index 47f3dd3c0383..3701566a20e8 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PythonBuiltinReferenceResolveProvider.java +++ b/python/src/com/jetbrains/python/psi/resolve/PythonBuiltinReferenceResolveProvider.java @@ -22,6 +22,7 @@ import com.jetbrains.python.psi.PyUtil; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.impl.references.PyReferenceImpl; +import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -46,8 +47,10 @@ public class PythonBuiltinReferenceResolveProvider implements PyReferenceResolve if (resultElement == null && "__builtins__".equals(referencedName)) { resultElement = bfile; // resolve __builtins__ reference } - if (resultElement != null) - result.add(new ImportedResolveResult(resultElement, PyReferenceImpl.getRate(resultElement), definers)); + if (resultElement != null) { + final TypeEvalContext typeEvalContext = TypeEvalContext.codeInsightFallback(element.getProject()); + result.add(new ImportedResolveResult(resultElement, PyReferenceImpl.getRate(resultElement, typeEvalContext), definers)); + } } return result; } diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveProcessor.java b/python/src/com/jetbrains/python/psi/resolve/ResolveProcessor.java deleted file mode 100644 index 4f00bfa399f1..000000000000 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveProcessor.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2000-2014 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.resolve; - -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiNamedElement; -import com.intellij.psi.ResolveState; -import com.intellij.psi.scope.PsiScopeProcessor; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.PyNames; -import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.impl.PyImportedModule; -import com.intellij.psi.util.QualifiedName; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -import static com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil.getScopeOwner; - -public class ResolveProcessor implements PsiScopeProcessor { - @NotNull private final String myName; - private PsiElement myResult = null; - private final List myDefiners; - private boolean myLocalResolve = false; - - public ResolveProcessor(@NotNull final String name) { - myName = name; - myDefiners = new ArrayList(2); // 1 is typical, 2 is sometimes, more is rare. - } - - public PsiElement getResult() { - return myResult; - } - - /** - * Adds a NameDefiner point which is a secondary resolution target. E.g. import statement for imported name. - * - * @param definer - */ - protected void addNameDefiner(PsiElement definer) { - myDefiners.add(definer); - } - - public List getDefiners() { - return myDefiners; - } - - public void setLocalResolve() { - myLocalResolve = true; - } - - public String toString() { - return PyUtil.nvl(myName) + ", " + PyUtil.nvl(myResult); - } - - @Override - public boolean execute(@NotNull PsiElement element, @NotNull ResolveState substitutor) { - if (element instanceof PyFile) { - final VirtualFile file = ((PyFile)element).getVirtualFile(); - if (file != null) { - if (myName.equals(file.getNameWithoutExtension())) { - return setResult(element, null); - } - else if (PyNames.INIT_DOT_PY.equals(file.getName())) { - VirtualFile dir = file.getParent(); - if ((dir != null) && myName.equals(dir.getName())) { - return setResult(element, null); - } - } - } - } - if (element instanceof PsiNamedElement) { - if (myName.equals(((PsiNamedElement)element).getName())) { - return setResult(element, null); - } - } - if (element instanceof PyReferenceExpression) { - PyReferenceExpression expr = (PyReferenceExpression)element; - String referencedName = expr.getReferencedName(); - if (referencedName != null && referencedName.equals(myName)) { - return setResult(element, null); - } - } - if (element instanceof NameDefiner) { - final NameDefiner definer = (NameDefiner)element; - PsiElement byName = resolveFromNameDefiner(definer); - if (byName != null) { - // prefer more specific imported modules to less specific ones - if (byName instanceof PyImportedModule && myResult instanceof PyImportedModule && - ((PyImportedModule)byName).isAncestorOf((PyImportedModule)myResult)) { - return false; - } - - setResult(byName, definer); - if (!PsiTreeUtil.isAncestor(element, byName, true)) { - addNameDefiner(definer); - } - // we can have same module imported directly and as part of chain (import os; import os.path) - // direct imports always take precedence over imported modules - // also, if some name is defined both in 'try' and 'except' parts of the same try/except statement, - // we prefer the declaration in the 'try' part - if (!(myResult instanceof PyImportedModule) && PsiTreeUtil.getParentOfType(element, PyExceptPart.class) == null) { - return false; - } - } - else if (element instanceof PyImportElement) { - final PyImportElement importElement = (PyImportElement) element; - final QualifiedName qName = importElement.getImportedQName(); - // http://stackoverflow.com/questions/6048786/from-module-import-in-init-py-makes-module-name-visible - if (qName != null && qName.getComponentCount() > 1 && myName.equals(qName.getLastComponent()) && - PyNames.INIT_DOT_PY.equals(importElement.getContainingFile().getName())) { - final PsiElement packageElement = ResolveImportUtil.resolveImportElement(importElement, qName.removeLastComponent()); - if (PyUtil.turnDirIntoInit(packageElement) == importElement.getContainingFile()) { - myResult = PyUtil.turnDirIntoInit(importElement.resolve()); - addNameDefiner(importElement); - } - } - - // name is resolved to unresolved import (PY-956) - String definedName = importElement.getAsName(); - if (definedName == null) { - if (qName != null && qName.getComponentCount() == 1) { - definedName = qName.getComponents().get(0); - } - } - if (myName.equals(definedName)) { - addNameDefiner(importElement); - } - element = importElement.getContainingImportStatement(); - } - } - if (element instanceof PyFromImportStatement && PyNames.INIT_DOT_PY.equals(element.getContainingFile().getName())) { - final PyFromImportStatement fromImportStatement = (PyFromImportStatement)element; - final QualifiedName qName = fromImportStatement.getImportSourceQName(); - if (qName != null && qName.endsWith(myName)) { - final PsiElement source = PyUtil.turnInitIntoDir(fromImportStatement.resolveImportSource()); - if (source != null && source.getParent() == element.getContainingFile().getContainingDirectory()) { - myResult = source; - addNameDefiner(fromImportStatement); - } - } - } - - return true; - } - - @Nullable - private PsiElement resolveFromNameDefiner(NameDefiner definer) { - if (myLocalResolve) { - if (definer instanceof PyImportElement) { - return ((PyImportElement) definer).getElementNamed(myName, false); - } - else if (definer instanceof PyStarImportElement) { - return null; - } - } - return definer.getElementNamed(myName); - } - - @Override - @Nullable - public T getHint(@NotNull Key hintKey) { - return null; - } - - @Override - public void handleEvent(@NotNull Event event, Object associated) { - } - - private boolean setResult(PsiElement result, @Nullable PsiElement definer) { - if (myResult == null || getScopeOwner(myResult) == getScopeOwner(result) || - (definer != null && getScopeOwner(myResult) == getScopeOwner(definer))) { - myResult = result; - } - return false; - } -} diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 69c5391700b6..8e57776277bb 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.psi.types; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.intellij.codeInsight.completion.CompletionUtil; @@ -445,14 +446,14 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { boolean isDefinition, @NotNull String name, @Nullable PyExpression location) { - final ResolveProcessor processor = new ResolveProcessor(name); + final PyResolveProcessor processor = new PyResolveProcessor(name); if (!isDefinition) { if (!cls.processInstanceLevelDeclarations(processor, location)) { - return processor.getResult(); + return Iterables.getFirst(processor.getElements(), null); } } cls.processClassLevelDeclarations(processor); - return processor.getResult(); + return Iterables.getFirst(processor.getElements(), null); } private static Key> CTX_VISITED = Key.create("PyClassType.Visited"); diff --git a/python/src/com/jetbrains/python/psi/types/PyImportedModuleType.java b/python/src/com/jetbrains/python/psi/types/PyImportedModuleType.java index 0aaa054c8b6c..74e693f65fb4 100644 --- a/python/src/com/jetbrains/python/psi/types/PyImportedModuleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyImportedModuleType.java @@ -60,8 +60,10 @@ public class PyImportedModuleType implements PyType { final PsiFile containingFile = location != null ? location.getContainingFile() : null; List elements = Collections.singletonList(ResolveImportUtil.resolveChild(resolved, name, containingFile, false, true)); final PyImportElement importElement = myImportedModule.getImportElement(); + final PyFile resolvedFile = PyUtil.as(resolved, PyFile.class); if (location != null && importElement != null && PyUtil.inSameFile(location, importElement) && - ResolveImportUtil.getPointInImport(location) == PointInImport.NONE && resolved instanceof PsiFileSystemItem) { + ResolveImportUtil.getPointInImport(location) == PointInImport.NONE && resolved instanceof PsiFileSystemItem && + (resolvedFile == null || !PyUtil.isPackage(resolvedFile) || resolvedFile.getElementNamed(name) == null)) { final List importedSubmodules = PyModuleType.collectImportedSubmodules((PsiFileSystemItem)resolved, location); if (importedSubmodules != null) { final Set imported = Sets.newHashSet(importedSubmodules); diff --git a/python/src/com/jetbrains/python/refactoring/PyDefUseUtil.java b/python/src/com/jetbrains/python/refactoring/PyDefUseUtil.java index a19d53ee22f0..579a4bb423fe 100644 --- a/python/src/com/jetbrains/python/refactoring/PyDefUseUtil.java +++ b/python/src/com/jetbrains/python/refactoring/PyDefUseUtil.java @@ -80,6 +80,13 @@ public class PyDefUseUtil { return ControlFlowUtil.Operation.CONTINUE; } } + else if (access == ReadWriteInstruction.ACCESS.IMPORT_STAR) { + final PyStarImportElement starImport = PyUtil.as(element, PyStarImportElement.class); + if (starImport != null && starImport.getElementNamed(varName) != null) { + result.add(rwInstruction); + return ControlFlowUtil.Operation.CONTINUE; + } + } } return ControlFlowUtil.Operation.NEXT; } diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/UsedUnresolvedNameImportedSeveralTimes/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection/UsedUnresolvedNameImportedSeveralTimes/a.py index 767f9bf68131..820a0f0ad6f3 100644 --- a/python/testData/inspections/PyUnresolvedReferencesInspection/UsedUnresolvedNameImportedSeveralTimes/a.py +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/UsedUnresolvedNameImportedSeveralTimes/a.py @@ -1,4 +1,4 @@ -from my_package.my_module import eggs +from my_package.my_module import eggs from my_package.my_module import eggs eggs() diff --git a/python/testData/inspections/unusedImport/unusedPackageAndSubmodule/test1.py b/python/testData/inspections/unusedImport/unusedPackageAndSubmodule/test1.py index c4ea15c0cde4..d5ac11d21f26 100644 --- a/python/testData/inspections/unusedImport/unusedPackageAndSubmodule/test1.py +++ b/python/testData/inspections/unusedImport/unusedPackageAndSubmodule/test1.py @@ -1,4 +1,4 @@ -import p1 +import p1 import p1.m1 print(p1.m1.a1) diff --git a/python/testData/optimizeImports/alphabeticalOrder.after.py b/python/testData/optimizeImports/alphabeticalOrder.after.py index 23f45f5cfcf3..edf016f2f333 100644 --- a/python/testData/optimizeImports/alphabeticalOrder.after.py +++ b/python/testData/optimizeImports/alphabeticalOrder.after.py @@ -12,7 +12,6 @@ import z from from a import C1 from alphabet import * -from alphabet import A from alphabet import B, A from alphabet import C from alphabet import D diff --git a/python/testData/refactoring/move/module/after/src/a.py b/python/testData/refactoring/move/module/after/src/a.py index 84c829adb049..9e7a4b733d13 100644 --- a/python/testData/refactoring/move/module/after/src/a.py +++ b/python/testData/refactoring/move/module/after/src/a.py @@ -1,5 +1,4 @@ import p1 -import p1.m1 from p1 import m1 from p1.m1 import foo diff --git a/python/testData/resolve/GlobalNotDefinedAtTopLevel.py b/python/testData/resolve/GlobalNotDefinedAtTopLevel.py new file mode 100644 index 000000000000..542137c1053d --- /dev/null +++ b/python/testData/resolve/GlobalNotDefinedAtTopLevel.py @@ -0,0 +1,4 @@ +def f(): + global foo + return foo +# \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index 64d1d5c7672f..00e9fc9b1c71 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -650,4 +650,8 @@ public class PyResolveTest extends PyResolveTestCase { assertTrue(target instanceof PyStringLiteralExpression); assertEquals("kwg", ((PyStringLiteralExpression)target).getStringValue()); } + + public void testGlobalNotDefinedAtTopLevel() { + assertResolvesTo(PyTargetExpression.class, "foo"); + } }