mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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.
This commit is contained in:
@@ -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<PyFunction> getTopLevelFunctions();
|
||||
|
||||
List<PyTargetExpression> getTopLevelAttributes();
|
||||
|
||||
/**
|
||||
@return an URL of file, maybe bogus if virtual file is not present.
|
||||
*/
|
||||
@NotNull
|
||||
String getUrl();
|
||||
}
|
||||
|
||||
@@ -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<String> 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();
|
||||
|
||||
@@ -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<String> 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<PyFromImportStatement> getFromImports() {
|
||||
public List<PyFromImportStatement> getFromImports(final Set<String> outerSeen) {
|
||||
final List<PyFromImportStatement> result = new ArrayList<PyFromImportStatement>();
|
||||
accept(new PyRecursiveElementVisitor() {
|
||||
Set<String> alreadySeen;
|
||||
{
|
||||
alreadySeen = new HashSet<String>();
|
||||
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
|
||||
|
||||
@@ -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<PsiElement> resolvePolicy = new RootPolicy<PsiElement>() {
|
||||
/*
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from pkg import token
|
||||
|
||||
x = token
|
||||
# <ref>
|
||||
@@ -0,0 +1 @@
|
||||
from submodule import token # import and reexport
|
||||
@@ -0,0 +1,2 @@
|
||||
def token():
|
||||
pass
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user