From 1a19cbf99b40f524cb090998b51273ecb3132307 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Tue, 24 May 2011 14:21:00 +0400 Subject: [PATCH] subpackage names are visible in the __init__.py of a package even if something was imported from the subpackages, and the subpackages themselves weren't imported (related to PY-3201) --- .../DeclarationConflictChecker.java | 3 +- .../PyUnresolvedReferencesInspection.java | 2 +- .../src/com/jetbrains/python/psi/PyUtil.java | 8 +++++ .../jetbrains/python/psi/impl/PyFileImpl.java | 30 ++++++++++------- .../python/psi/impl/PyReferenceImpl.java | 2 +- .../psi/resolve/ImportedResolveResult.java | 7 ++-- .../python/psi/resolve/PyResolveUtil.java | 4 ++- .../python/psi/resolve/ResolveImportUtil.java | 2 +- .../python/psi/resolve/ResolveProcessor.java | 33 +++++++++++++++---- .../ImportPackageIntoSelf.py | 4 +++ .../mygame/__init__.py | 5 +++ .../mygame/display.py | 1 + .../ImportPackageIntoSelf.py | 4 +++ .../mygame/__init__.py | 5 +++ .../mygame/display.py | 1 + .../python/PyMultiFileResolveTest.java | 24 +++++++++++++- .../com/jetbrains/python/PyStubsTest.java | 16 +++++++++ 17 files changed, 123 insertions(+), 28 deletions(-) create mode 100644 python/testData/resolve/multiFile/fromImportPackageIntoSelf/ImportPackageIntoSelf.py create mode 100644 python/testData/resolve/multiFile/fromImportPackageIntoSelf/mygame/__init__.py create mode 100644 python/testData/resolve/multiFile/fromImportPackageIntoSelf/mygame/display.py create mode 100644 python/testData/resolve/multiFile/importPackageIntoSelfInit/ImportPackageIntoSelf.py create mode 100644 python/testData/resolve/multiFile/importPackageIntoSelfInit/mygame/__init__.py create mode 100644 python/testData/resolve/multiFile/importPackageIntoSelfInit/mygame/display.py diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java b/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java index 3e1ad8bd3bdc..692762603efa 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java @@ -9,7 +9,6 @@ import com.intellij.usages.UsageTarget; import com.intellij.usages.UsageViewManager; import com.intellij.usages.UsageViewPresentation; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.psi.NameDefiner; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.resolve.ResolveProcessor; import org.jetbrains.annotations.NotNull; @@ -46,7 +45,7 @@ public class DeclarationConflictChecker { PyResolveUtil.treeCrawlUp(processor, ref.getElement()); PsiElement result = processor.getResult(); if (result != null) { - List definers = processor.getDefiners(); + 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. } diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index b973f3e26fb8..c0db5ac02250 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -87,7 +87,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } public static class Visitor extends PyInspectionVisitor { - private Set myUsedImports = Collections.synchronizedSet(new HashSet()); + private Set myUsedImports = Collections.synchronizedSet(new HashSet()); private Set myAllImports = Collections.synchronizedSet(new HashSet()); private final ImmutableSet myIgnoredIdentifiers; diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 42cf7ce1e303..8ce2a4eb448c 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -610,6 +610,14 @@ public class PyUtil { } // don't touch non-dirs } + @Nullable + public static PsiElement turnInitIntoDir(PsiElement target) { + if (target instanceof PyFile && PyNames.INIT_DOT_PY.equals(((PyFile) target).getName())) { + return ((PyFile) target).getContainingDirectory(); + } + return target; + } + /** * Counts initial underscores of an identifier. * diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java index 89283a294880..2bcc3f60e3b5 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java @@ -15,8 +15,6 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.indexing.FileBasedIndex; import com.jetbrains.python.*; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; -import com.jetbrains.python.codeInsight.dataflow.scope.Scope; -import com.jetbrains.python.codeInsight.dataflow.scope.impl.ScopeImpl; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.resolve.ResolveImportUtil; @@ -32,7 +30,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.lang.ref.SoftReference; import java.util.*; public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { @@ -248,12 +245,12 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { } @Nullable - private static PsiElement findNameInFromImportStatementStub(String name, PyFromImportStatementStub child) { - if (child.isStarImport()) { + private PsiElement findNameInFromImportStatementStub(String name, PyFromImportStatementStub stub) { + if (stub.isStarImport()) { if (PyUtil.isClassPrivateName(name)) { return null; } - final PyFromImportStatement statement = child.getPsi(); + final PyFromImportStatement statement = stub.getPsi(); PsiElement starImportSource = ResolveImportUtil.resolveFromImportStatementSource(statement); if (starImportSource != null) { starImportSource = PyUtil.turnDirIntoInit(starImportSource); @@ -266,7 +263,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { } } else { - final List importElements = child.getChildrenStubs(); + final List importElements = stub.getChildrenStubs(); for (StubElement importElement : importElements) { final PsiElement psi = importElement.getPsi(); if (psi instanceof PyImportElement && name.equals(((PyImportElement)psi).getVisibleName())) { @@ -277,6 +274,16 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { } } } + // http://stackoverflow.com/questions/6048786/from-module-import-in-init-py-makes-module-name-visible + if (PyNames.INIT_DOT_PY.equals(getName())) { + final PyQualifiedName qName = stub.getImportSourceQName(); + if (qName.endsWith(name)) { + final PsiElement element = PyUtil.turnInitIntoDir(ResolveImportUtil.resolveFromImportStatementSource(stub.getPsi())); + if (element != null && element.getParent() == getContainingDirectory()) { + return element; + } + } + } return null; } @@ -302,10 +309,11 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { } return new PyImportedModule(this, PyQualifiedName.fromComponents(name)); } - if (name.equals(((PyImportElement)psi).getVisibleName())) { - final PsiElement resolved = importElement.getElementNamed(name); - if (resolved != null) { - return resolved; + // http://stackoverflow.com/questions/6048786/from-module-import-in-init-py-makes-module-name-visible + if (qName.getComponentCount() > 1 && name.equals(qName.getLastComponent()) && PyNames.INIT_DOT_PY.equals(getName())) { + final PsiElement element = ResolveImportUtil.resolveImportElement(importElement, qName.removeLastComponent()); + if (PyUtil.turnDirIntoInit(element) == this) { + return importElement; } } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java index 610d5329134e..638a473792b9 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java @@ -171,7 +171,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference PsiElement uexpr = PyResolveUtil.treeCrawlUp(processor, false, realContext, roof); if ((uexpr != null)) { // sort what we got - for (NameDefiner hit : processor.getDefiners()) { + for (PsiElement hit : processor.getDefiners()) { ret.poke(hit, getRate(hit)); } uexpr = PyUtil.turnDirIntoInit(uexpr); // an import statement may have returned a dir diff --git a/python/src/com/jetbrains/python/psi/resolve/ImportedResolveResult.java b/python/src/com/jetbrains/python/psi/resolve/ImportedResolveResult.java index 79af223db808..6084ed5711b0 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ImportedResolveResult.java +++ b/python/src/com/jetbrains/python/psi/resolve/ImportedResolveResult.java @@ -1,7 +1,6 @@ package com.jetbrains.python.psi.resolve; import com.intellij.psi.PsiElement; -import com.jetbrains.python.psi.NameDefiner; import java.util.List; @@ -9,14 +8,14 @@ import java.util.List; * @author yole */ public class ImportedResolveResult extends RatedResolveResult { - private final List myNameDefiners; + private final List myNameDefiners; - public ImportedResolveResult(PsiElement element, int rate, List nameDefiners) { + public ImportedResolveResult(PsiElement element, int rate, List nameDefiners) { super(rate, element); myNameDefiners = nameDefiners; } - public List getNameDefiners() { + public List getNameDefiners() { return myNameDefiners; } diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java index 0b2ec2a4b070..2660be443c44 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java @@ -91,7 +91,9 @@ public class PyResolveUtil { PyElementTypes.ASSIGNMENT_STATEMENT, PyElementTypes.PARAMETER_LIST, PyElementTypes.EXCEPT_PART, - PyElementTypes.FOR_STATEMENT); + PyElementTypes.FOR_STATEMENT, + PyElementTypes.FROM_IMPORT_STATEMENT); // this is not exactly a NameDefiner but needed anyway + // in mypackage/__init__.py, 'from mypackage.foo import bar' makes 'foo' name visible /** * Crawls up the PSI tree, checking nodes as if crawling backwards through source lexemes. diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java index 6242dcee5047..12691d3d5c9d 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java @@ -190,7 +190,7 @@ public class ResolveImportUtil { } @NotNull - private static List resolveFromImportStatementSource(PyFromImportStatement from_import_statement, PyQualifiedName qName) { + public static List resolveFromImportStatementSource(PyFromImportStatement from_import_statement, PyQualifiedName qName) { boolean absolute_import_enabled = isAbsoluteImportEnabledFor(from_import_statement); PsiFile file = from_import_statement.getContainingFile(); return resolveModule(qName, file, absolute_import_enabled, from_import_statement.getRelativeLevel()); diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveProcessor.java b/python/src/com/jetbrains/python/psi/resolve/ResolveProcessor.java index be2d819d97f7..25bf9567efb2 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveProcessor.java +++ b/python/src/com/jetbrains/python/psi/resolve/ResolveProcessor.java @@ -20,11 +20,11 @@ import java.util.List; public class ResolveProcessor implements PsiScopeProcessor { @NotNull private final String myName; private PsiElement myResult = null; - private final List myDefiners; + private final List myDefiners; public ResolveProcessor(@NotNull final String name) { myName = name; - myDefiners = new ArrayList(2); // 1 is typical, 2 is sometimes, more is rare. + myDefiners = new ArrayList(2); // 1 is typical, 2 is sometimes, more is rare. } public PsiElement getResult() { @@ -36,11 +36,11 @@ public class ResolveProcessor implements PsiScopeProcessor { * * @param definer */ - protected void addNameDefiner(NameDefiner definer) { + protected void addNameDefiner(PsiElement definer) { myDefiners.add(definer); } - public List getDefiners() { + public List getDefiners() { return myDefiners; } @@ -98,11 +98,21 @@ public class ResolveProcessor implements PsiScopeProcessor { } } else if (element instanceof PyImportElement) { - // name is resolved to unresolved import (PY-956) final PyImportElement importElement = (PyImportElement) element; + final PyQualifiedName 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(ResolveImportUtil.resolveImportElement(importElement)); + addNameDefiner(importElement); + } + } + + // name is resolved to unresolved import (PY-956) String definedName = importElement.getAsName(); if (definedName == null) { - final PyQualifiedName qName = importElement.getImportedQName(); if (qName != null && qName.getComponentCount() == 1) { definedName = qName.getComponents().get(0); } @@ -112,6 +122,17 @@ public class ResolveProcessor implements PsiScopeProcessor { } } } + else if (element instanceof PyFromImportStatement && PyNames.INIT_DOT_PY.equals(element.getContainingFile().getName())) { + final PyFromImportStatement fromImportStatement = (PyFromImportStatement)element; + final PyQualifiedName qName = fromImportStatement.getImportSourceQName(); + if (qName != null && qName.endsWith(myName)) { + final PsiElement source = PyUtil.turnInitIntoDir(ResolveImportUtil.resolveFromImportStatementSource(fromImportStatement)); + if (source != null && source.getParent() == element.getContainingFile().getContainingDirectory()) { + myResult = source; + addNameDefiner(fromImportStatement); + } + } + } return true; } diff --git a/python/testData/resolve/multiFile/fromImportPackageIntoSelf/ImportPackageIntoSelf.py b/python/testData/resolve/multiFile/fromImportPackageIntoSelf/ImportPackageIntoSelf.py new file mode 100644 index 000000000000..6576c9991b24 --- /dev/null +++ b/python/testData/resolve/multiFile/fromImportPackageIntoSelf/ImportPackageIntoSelf.py @@ -0,0 +1,4 @@ +import mygame + +mygame.display.foo() +# diff --git a/python/testData/resolve/multiFile/fromImportPackageIntoSelf/mygame/__init__.py b/python/testData/resolve/multiFile/fromImportPackageIntoSelf/mygame/__init__.py new file mode 100644 index 000000000000..829cf64c1740 --- /dev/null +++ b/python/testData/resolve/multiFile/fromImportPackageIntoSelf/mygame/__init__.py @@ -0,0 +1,5 @@ +from display import foo + +print('calling display.foo()') +display.foo() +# diff --git a/python/testData/resolve/multiFile/fromImportPackageIntoSelf/mygame/display.py b/python/testData/resolve/multiFile/fromImportPackageIntoSelf/mygame/display.py new file mode 100644 index 000000000000..ec73b3593e51 --- /dev/null +++ b/python/testData/resolve/multiFile/fromImportPackageIntoSelf/mygame/display.py @@ -0,0 +1 @@ +def foo(): print 'foo' diff --git a/python/testData/resolve/multiFile/importPackageIntoSelfInit/ImportPackageIntoSelf.py b/python/testData/resolve/multiFile/importPackageIntoSelfInit/ImportPackageIntoSelf.py new file mode 100644 index 000000000000..6576c9991b24 --- /dev/null +++ b/python/testData/resolve/multiFile/importPackageIntoSelfInit/ImportPackageIntoSelf.py @@ -0,0 +1,4 @@ +import mygame + +mygame.display.foo() +# diff --git a/python/testData/resolve/multiFile/importPackageIntoSelfInit/mygame/__init__.py b/python/testData/resolve/multiFile/importPackageIntoSelfInit/mygame/__init__.py new file mode 100644 index 000000000000..2a7a6b055c93 --- /dev/null +++ b/python/testData/resolve/multiFile/importPackageIntoSelfInit/mygame/__init__.py @@ -0,0 +1,5 @@ +import mygame.display + +display.foo() +# + diff --git a/python/testData/resolve/multiFile/importPackageIntoSelfInit/mygame/display.py b/python/testData/resolve/multiFile/importPackageIntoSelfInit/mygame/display.py new file mode 100644 index 000000000000..b937ba6ac582 --- /dev/null +++ b/python/testData/resolve/multiFile/importPackageIntoSelfInit/mygame/display.py @@ -0,0 +1 @@ +def foo(): pass diff --git a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java index 241032d73041..93f3b1ca5936 100644 --- a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java @@ -13,6 +13,7 @@ import com.jetbrains.python.psi.*; * @author yole */ public class PyMultiFileResolveTest extends PyResolveTestCase { + protected String myTestFileName; private static void checkInitPyDir(PsiElement elt, String dirname) { assertTrue(elt instanceof PyFile); @@ -223,6 +224,27 @@ public class PyMultiFileResolveTest extends PyResolveTestCase { assertResolvesTo(PyFunction.class, "foo", "/src/mygame/display.py"); } + public void testImportPackageIntoSelfInit() { + myTestFileName = "mygame/__init__.py"; + try { + assertResolvesTo(PyFile.class, "display.py"); + } + finally { + myTestFileName = null; + } + } + + public void testFromImportPackageIntoSelf() { + myTestFileName = "mygame/__init__.py"; + try { + assertResolvesTo(PyFile.class, "display.py"); + } + finally { + myTestFileName = null; + } + } + + public void testImportPrivateNameWithStar() { // PY-2717 PsiElement psiElement = doResolve(); assertNull(psiElement); @@ -230,7 +252,7 @@ public class PyMultiFileResolveTest extends PyResolveTestCase { private PsiFile prepareFile() { String testName = getTestName(true); - String fileName = getTestName(false) + ".py"; + String fileName = myTestFileName != null ? myTestFileName : getTestName(false) + ".py"; myFixture.copyDirectoryToProject(testName, ""); PsiDocumentManager.getInstance(myFixture.getProject()).commitAllDocuments(); diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index 6d165f5875c0..0559b8278ea4 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -279,6 +279,22 @@ public class PyStubsTest extends PyLightFixtureTestCase { assertNotParsed(file); } + public void testInitPyExportsSubpackageImport() { + myFixture.copyDirectoryToProject("../resolve/multiFile/importPackageIntoSelf", ""); + final PyFile initPy = (PyFile) myFixture.getPsiManager().findFile(myFixture.findFileInTempDir("mygame/__init__.py")); + final PsiElement element = initPy.findExportedName("display"); + assertTrue(element instanceof PyImportElement); + assertNotParsed(initPy); + } + + public void testInitPyExportsSubpackageFromImport() { + myFixture.copyDirectoryToProject("../resolve/multiFile/fromImportPackageIntoSelf", ""); + final PyFile initPy = (PyFile) myFixture.getPsiManager().findFile(myFixture.findFileInTempDir("mygame/__init__.py")); + final PsiElement element = initPy.findExportedName("display"); + assertTrue(element instanceof PyFile); + assertNotParsed(initPy); + } + public void testVariableIndex() { getTestFile(); GlobalSearchScope scope = GlobalSearchScope.allScope(myFixture.getProject());