From d538ddb5f5e399f161012a8a280506b744b9c716 Mon Sep 17 00:00:00 2001 From: Dmitry Cheryasov Date: Wed, 28 May 2008 08:55:29 +0400 Subject: [PATCH] Preliminarily closes PY-12 and PY-8. Import resolution is still quite suboptimal, though. PyFile and PyFileImpl: added getUrl() method. PyResolveUtil and PyFile: more of (lame) means to squash SOEs on circular imports. ResolveImportUtil: actual PY-12 resolve order fix. PyMultiFileResolveTest and friends: a test case for name resolution inside subpackage. --- .../src/com/jetbrains/python/psi/PyFile.java | 7 ++++ .../jetbrains/python/psi/PyResolveUtil.java | 19 +++++++++- .../jetbrains/python/psi/impl/PyFileImpl.java | 35 +++++++++++++++---- .../python/psi/impl/ResolveImportUtil.java | 26 +++++++++++--- .../multiFile/resolveInPkg/ResolveInPkg.py | 4 +++ .../multiFile/resolveInPkg/pkg/__init__.py | 1 + .../multiFile/resolveInPkg/pkg/submodule.py | 2 ++ .../python/PyMultiFileResolveTest.java | 5 +++ 8 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 python/testData/resolve/multiFile/resolveInPkg/ResolveInPkg.py create mode 100644 python/testData/resolve/multiFile/resolveInPkg/pkg/__init__.py create mode 100644 python/testData/resolve/multiFile/resolveInPkg/pkg/submodule.py diff --git a/python/src/com/jetbrains/python/psi/PyFile.java b/python/src/com/jetbrains/python/psi/PyFile.java index 72678153be7f..e039099c8b32 100644 --- a/python/src/com/jetbrains/python/psi/PyFile.java +++ b/python/src/com/jetbrains/python/psi/PyFile.java @@ -5,6 +5,7 @@ package com.jetbrains.python.psi; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; import java.util.List; @@ -19,4 +20,10 @@ public interface PyFile extends PyElement, PsiFile { List getTopLevelFunctions(); List getTopLevelAttributes(); + + /** + @return an URL of file, maybe bogus if virtual file is not present. + */ + @NotNull + String getUrl(); } diff --git a/python/src/com/jetbrains/python/psi/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/PyResolveUtil.java index 5905c87f6de3..d942a16b3656 100644 --- a/python/src/com/jetbrains/python/psi/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/PyResolveUtil.java @@ -63,6 +63,10 @@ public class PyResolveUtil { if ((processor instanceof ResolveProcessor) && !(((ResolveProcessor)processor).approve(cur))) { return null; } + /* // resolution debug tracker + if (cur instanceof PsiFile) System.out.println(processor.toString() + ": " + cur.toString()); + else System.out.println(processor.toString() + ": " + _fmt_node(cur)); + */ if (!cur.processDeclarations(processor, ResolveState.initial(), cur == elt ? lastParent : null, elt)) { if (processor instanceof ResolveProcessor) { return ((ResolveProcessor)processor).getResult(); @@ -140,7 +144,20 @@ public class PyResolveUtil { public PsiElement getResult() { return myResult; } - + + static String _nvl(Object s) { + if (s != null) return "'" + s.toString() + "'"; + else return "null"; + } + + public Set getSeen() { + return mySeen; + } + + public String toString() { + return _nvl(myName) + ", " + _nvl(myResult); + } + public boolean execute(PsiElement element, ResolveState substitutor) { if (element instanceof PyFile) { final VirtualFile file = ((PyFile)element).getVirtualFile(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java index 94405a00e083..82bad5ceea94 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java @@ -18,10 +18,8 @@ package com.jetbrains.python.psi.impl; import com.intellij.extapi.psi.PsiFileBase; import com.intellij.openapi.fileTypes.FileType; -import com.intellij.psi.FileViewProvider; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.ResolveState; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.tree.IElementType; @@ -35,7 +33,9 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class PyFileImpl extends PsiFileBase implements PyFile { public PyFileImpl(FileViewProvider viewProvider) { @@ -50,6 +50,15 @@ public class PyFileImpl extends PsiFileBase implements PyFile { public String toString() { return "PyFile:" + getName(); } + + @NotNull + public String getUrl() { + String fname; + VirtualFile vfile = getVirtualFile(); + if (vfile != null) fname = vfile.getUrl(); + else fname = "(null)://" + ((Object)this).toString(); + return fname; + } public Icon getIcon(int flags) { return PythonFileType.INSTANCE.getIcon(); @@ -87,7 +96,11 @@ public class PyFileImpl extends PsiFileBase implements PyFile { if (!processor.execute(e, substitutor)) return false; } - for(PyFromImportStatement e: getFromImports()) { + Set procSeen = null; + if (processor instanceof PyResolveUtil.ResolveProcessor) { + procSeen = ((PyResolveUtil.ResolveProcessor)processor).getSeen(); + } + for(PyFromImportStatement e: getFromImports(procSeen)) { if (e == lastParent) continue; if (!e.processDeclarations(processor, substitutor, null, this)) return false; } @@ -153,10 +166,20 @@ public class PyFileImpl extends PsiFileBase implements PyFile { return ret; } - public List getFromImports() { + public List getFromImports(final Set outerSeen) { final List result = new ArrayList(); accept(new PyRecursiveElementVisitor() { + Set alreadySeen; + { + alreadySeen = new HashSet(); + if (outerSeen != null) alreadySeen.addAll(outerSeen); + } public void visitPyElement(final PyElement node) { + if (node instanceof PyFile) { + final String fname = ((PyFile)node).getUrl(); + if (alreadySeen.contains(fname)) return; // don't descend into the same file again + else alreadySeen.add(fname); + } super.visitPyElement(node); if (PyFromImportStatement.class.isInstance(node)) { //noinspection unchecked diff --git a/python/src/com/jetbrains/python/psi/impl/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/impl/ResolveImportUtil.java index bd0c6dbdd478..fe8e3aebb358 100644 --- a/python/src/com/jetbrains/python/psi/impl/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/impl/ResolveImportUtil.java @@ -54,13 +54,14 @@ public class ResolveImportUtil { /* True resolve order is: - local modules, - - builtins (in fact), + - builtins? (check), - modules from sys.path (aka SdkOrderEntries). (http://docs.python.org/ref/import.html) */ // TODO: assume some things like sys to be only from __builtins__ - // FIXME: resolve sources relatively to file in hand, not project root - + // TODO: rewrite entirely imitating Python import process: global module table, under-initialisation, etc. + + // qualified imports resolve their children final PyExpression qualifier = importRef.getQualifier(); if (qualifier instanceof PyReferenceExpression) { PsiElement qualifierElement = ((PyReferenceExpression) qualifier).resolve(); @@ -71,15 +72,29 @@ public class ResolveImportUtil { if (importFrom != null) { return resolveChild(importFrom, referencedName, importRef); } - + + // unqualified import can be found: + // in the same dir + final PsiFile pfile = importRef.getContainingFile(); + if (pfile != null) { + PsiDirectory pdir = pfile.getContainingDirectory(); + if (pdir != null) { + PsiElement elt = resolveChild(pdir, referencedName, importRef); + if (elt != null) return elt; + } + + } + + // .. or in SDK roots final Module module = ModuleUtil.findModuleForPsiElement(importRef); if (module != null) { RootPolicy resolvePolicy = new RootPolicy() { + /* public PsiElement visitModuleSourceOrderEntry(final ModuleSourceOrderEntry moduleOrderEntry, final PsiElement value) { if (value != null) return value; return resolveInRoots(moduleOrderEntry.getRootModel().getContentRoots(), referencedName, importRef); } - + */ public PsiElement visitJdkOrderEntry(final JdkOrderEntry jdkOrderEntry, final PsiElement value) { if (value != null) return value; return resolveInRoots(jdkOrderEntry.getRootFiles(OrderRootType.SOURCES), referencedName, importRef); @@ -197,6 +212,7 @@ public class ResolveImportUtil { if (subdir != null) return subdir; else { // not a subdir, not a file; could be a name in parent/__init__.py final PsiFile initPy = dir.findFile(INIT_PY); + if ((importRef != null) && (initPy == importRef.getContainingFile())) return ret; // don't dive into the file we're in if (initPy != null) { if (processor == null) processor = new PyResolveUtil.ResolveProcessor(referencedName); // should not normally happen return PyResolveUtil.treeWalkUp(processor, initPy, null, importRef); diff --git a/python/testData/resolve/multiFile/resolveInPkg/ResolveInPkg.py b/python/testData/resolve/multiFile/resolveInPkg/ResolveInPkg.py new file mode 100644 index 000000000000..79b1a683e228 --- /dev/null +++ b/python/testData/resolve/multiFile/resolveInPkg/ResolveInPkg.py @@ -0,0 +1,4 @@ +from pkg import token + +x = token +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/resolveInPkg/pkg/__init__.py b/python/testData/resolve/multiFile/resolveInPkg/pkg/__init__.py new file mode 100644 index 000000000000..87833b8fbdc1 --- /dev/null +++ b/python/testData/resolve/multiFile/resolveInPkg/pkg/__init__.py @@ -0,0 +1 @@ +from submodule import token # import and reexport \ No newline at end of file diff --git a/python/testData/resolve/multiFile/resolveInPkg/pkg/submodule.py b/python/testData/resolve/multiFile/resolveInPkg/pkg/submodule.py new file mode 100644 index 000000000000..767c80a0b89e --- /dev/null +++ b/python/testData/resolve/multiFile/resolveInPkg/pkg/submodule.py @@ -0,0 +1,2 @@ +def token(): + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java index 19994af68329..9840df2635f5 100644 --- a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java @@ -84,6 +84,11 @@ public class PyMultiFileResolveTest extends CodeInsightTestCase { assertTrue(element instanceof PyTargetExpression); } + public void testResolveInPkg() throws Exception { + PsiElement element = doResolve(); + assertTrue(element instanceof PyFunction); + } + private PsiElement doResolve() throws Exception { String testName = getTestName(true); String fileName = getTestName(false) + ".py";