Fixed unresolved references for namespace packages (PY-2813)

This commit is contained in:
Andrey Vlasovskikh
2012-04-30 20:17:02 +04:00
parent ff7c083a42
commit 3e9550fd41
29 changed files with 163 additions and 26 deletions

View File

@@ -639,6 +639,32 @@ public class PyUtil {
return turnInitIntoDir(file) != null;
}
@Nullable
public static PsiElement getPackageElement(@NotNull PsiDirectory directory) {
if (isPackage(directory)) {
final PsiElement init = turnDirIntoInit(directory);
if (init != null) {
return init;
}
return directory;
}
return null;
}
private static boolean hasNamespacePackageFile(@NotNull PsiDirectory directory) {
final String name = directory.getName().toLowerCase();
final PsiDirectory parent = directory.getParent();
if (parent != null) {
for (PsiFile file : parent.getFiles()) {
final String filename = file.getName().toLowerCase();
if (filename.startsWith(name) && filename.endsWith("-nspkg.pth")) {
return true;
}
}
}
return false;
}
/**
* Counts initial underscores of an identifier.
*

View File

@@ -4,8 +4,9 @@ import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.EmptyIterable;
@@ -200,10 +201,16 @@ public class PyImportElementImpl extends PyBaseElementImpl<PyImportElementStub>
return null;
}
if (qName.getComponentCount() == 1) {
return resolveImportElement ? ResolveImportUtil.resolveImportElement(this, PyQualifiedName.fromComponents(name)) : this;
if (resolveImportElement) {
final PsiElement element = ResolveImportUtil.resolveImportElement(this, PyQualifiedName.fromComponents(name));
if (element instanceof PsiDirectory) {
return createImportedModule(name);
}
return element;
}
return this;
}
final PsiNamedElement container = getStubOrPsiParentOfType(PsiNamedElement.class);
return new PyImportedModule(this, container, PyQualifiedName.fromComponents(name));
return createImportedModule(name);
}
}
@@ -221,4 +228,13 @@ public class PyImportElementImpl extends PyBaseElementImpl<PyImportElementStub>
final PyQualifiedName qName = getImportedQName();
return String.format("%s:%s", super.toString(), qName != null ? qName : "null");
}
@Nullable
private PsiElement createImportedModule(String name) {
final PsiFile file = getContainingFile();
if (file instanceof PyFile) {
return new PyImportedModule(this, (PyFile)file, PyQualifiedName.fromComponents(name));
}
return null;
}
}

View File

@@ -1,5 +1,6 @@
package com.jetbrains.python.psi.impl;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.impl.light.LightElement;
@@ -17,20 +18,23 @@ import java.util.List;
*/
public class PyImportedModule extends LightElement implements NameDefiner {
@Nullable private PyImportElement myImportElement;
private final PsiElement myContainer;
private final PyQualifiedName myImportedPrefix;
@NotNull private final PyFile myContainingFile;
@NotNull private final PyQualifiedName myImportedPrefix;
public PyImportedModule(@Nullable PyImportElement importElement, PsiElement container, PyQualifiedName importedPrefix) {
super(container.getManager(), PythonLanguage.getInstance());
public PyImportedModule(@Nullable PyImportElement importElement, @NotNull PyFile containingFile, @NotNull PyQualifiedName importedPrefix) {
super(containingFile.getManager(), PythonLanguage.getInstance());
myImportElement = importElement;
myContainer = container;
myContainingFile = containingFile;
myImportedPrefix = importedPrefix;
}
@NotNull
@Override
public PyFile getContainingFile() {
return (PyFile)myContainer.getContainingFile();
return myContainingFile;
}
@NotNull
public PyQualifiedName getImportedPrefix() {
return myImportedPrefix;
}
@@ -47,7 +51,7 @@ public class PyImportedModule extends LightElement implements NameDefiner {
if (qName != null && qName.getComponentCount() == prefix.getComponentCount()) {
return resolve(myImportElement, prefix);
}
return new PyImportedModule(myImportElement, myContainer, prefix);
return new PyImportedModule(myImportElement, myContainingFile, prefix);
}
final PyImportElement fromImportElement = findMatchingFromImport(myImportedPrefix, the_name);
if (fromImportElement != null) {
@@ -88,7 +92,7 @@ public class PyImportedModule extends LightElement implements NameDefiner {
}
public PsiElement copy() {
return new PyImportedModule(myImportElement, myContainer, myImportedPrefix);
return new PyImportedModule(myImportElement, myContainingFile, myImportedPrefix);
}
@Override
@@ -109,7 +113,7 @@ public class PyImportedModule extends LightElement implements NameDefiner {
}
@Nullable
public PyFile resolve() {
public PsiElement resolve() {
final PsiElement element;
if (myImportElement != null) {
element = ResolveImportUtil.resolveImportElement(myImportElement, myImportedPrefix);
@@ -117,11 +121,10 @@ public class PyImportedModule extends LightElement implements NameDefiner {
else {
element = ResolveImportUtil.resolveModuleInRoots(getImportedPrefix(), getContainingFile());
}
final PsiElement result = PyUtil.turnDirIntoInit(element);
if (result instanceof PyFile) {
return (PyFile)result;
if (element instanceof PsiDirectory) {
return PyUtil.getPackageElement((PsiDirectory)element);
}
return null;
return element;
}
@Nullable
@@ -130,6 +133,6 @@ public class PyImportedModule extends LightElement implements NameDefiner {
}
public boolean isAncestorOf(PyImportedModule other) {
return PsiTreeUtil.isAncestor(myContainer, other.myContainer, true);
return PsiTreeUtil.isAncestor(myContainingFile, other.myContainingFile, true);
}
}

View File

@@ -189,6 +189,10 @@ public class PyImportReference extends PyReferenceImpl {
}
return myObjects.toArray();
}
else if (mod_candidate instanceof PsiDirectory) {
fillFromDir((PsiDirectory)mod_candidate, ImportKeywordHandler.INSTANCE);
return myObjects.toArray();
}
}
else { // null source, must be a "from ... import"
relativeLevel = from_import.getRelativeLevel();

View File

@@ -132,7 +132,10 @@ public class ResolveImportUtil {
if (!candidate.isValid()) {
throw new PsiInvalidElementAccessException(candidate, "Got an invalid candidate from resolveImportSourceCandidates(): " + candidate.getClass());
}
PsiElement result = resolveChild(PyUtil.turnDirIntoInit(candidate), name, file, false, true);
if (candidate instanceof PsiDirectory) {
candidate = PyUtil.getPackageElement((PsiDirectory)candidate);
}
PsiElement result = resolveChild(candidate, name, file, false, true);
if (result != null) {
if (!result.isValid()) {
throw new PsiInvalidElementAccessException(result, "Got an invalid candidate from resolveChild(): " + result.getClass());
@@ -322,7 +325,7 @@ public class ResolveImportUtil {
if (referencedName == null) return null;
final PsiDirectory subdir = dir.findSubdirectory(referencedName);
if (subdir != null && (!checkForPackage || subdir.findFile(PyNames.INIT_DOT_PY) != null)) {
if (subdir != null && (!checkForPackage || PyUtil.isPackage(subdir))) {
return subdir;
}
@@ -357,8 +360,10 @@ public class ResolveImportUtil {
public static ResolveResultList rateResults(List<? extends PsiElement> targets) {
ResolveResultList ret = new ResolveResultList();
for (PsiElement target : targets) {
target = PyUtil.turnDirIntoInit(target);
if (target != null) { // ignore dirs without __init__.py, worthless
if (target instanceof PsiDirectory) {
target = PyUtil.getPackageElement((PsiDirectory)target);
}
if (target != null) { // Ignore non-package dirs, worthless
int rate = RatedResolveResult.RATE_HIGH;
if (target instanceof PyFile) {
VirtualFile vFile = ((PyFile)target).getVirtualFile();

View File

@@ -2,6 +2,8 @@ package com.jetbrains.python.psi.types;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
@@ -11,10 +13,12 @@ import com.jetbrains.python.psi.impl.PyImportedModule;
import com.jetbrains.python.psi.impl.PyQualifiedName;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -33,10 +37,15 @@ public class PyImportedModuleType implements PyType {
PyExpression location,
AccessDirection direction,
PyResolveContext resolveContext) {
final PyFile file = myImportedModule.resolve();
if (file != null) {
final PsiElement resolved = myImportedModule.resolve();
if (resolved instanceof PyFile) {
final PyFile file = (PyFile)resolved;
return new PyModuleType(file).resolveMember(name, location, direction, resolveContext);
}
else if (resolved instanceof PsiDirectory) {
final List<PsiElement> elements = Collections.singletonList(ResolveImportUtil.resolveChild(resolved, name, null, true, true));
return ResolveImportUtil.rateResults(elements);
}
return null;
}

View File

@@ -190,8 +190,10 @@ public class PyTypeParser {
final TextRange first = ranges.get(0);
for (TextRange range : ranges) {
final PyQualifiedName moduleName = PyQualifiedName.fromDottedString(first.union(range).substring(type));
moduleType = new PyImportedModuleType(new PyImportedModule(null, anchor.getContainingFile(), moduleName));
types.put(range.shiftRight(offset), moduleType);
if (anchorFile instanceof PyFile) {
moduleType = new PyImportedModuleType(new PyImportedModule(null, (PyFile)anchorFile, moduleName));
types.put(range.shiftRight(offset), moduleType);
}
}
}
final String shortName = classRange.substring(type);

View File

@@ -0,0 +1 @@
import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('p1',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('p1',types.ModuleType('p1')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p)

View File

@@ -0,0 +1 @@
from p1 import m1

View File

@@ -0,0 +1 @@
from p1 import <caret>

View File

@@ -0,0 +1,2 @@
def foo():
pass

View File

@@ -0,0 +1 @@
import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('p1',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('p1',types.ModuleType('p1')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p)

View File

@@ -0,0 +1,4 @@
import p1.m1 #pass
print(p1.m1) #pass
print(p1.<warning descr="Cannot find reference 'm2' in 'imported module p1'">m2</warning>) #fail

View File

@@ -0,0 +1,4 @@
from p1.m1 import foo
foo()
#<ref>

View File

@@ -0,0 +1 @@
import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('p1',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('p1',types.ModuleType('p1')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p)

View File

@@ -0,0 +1,2 @@
def foo():
pass

View File

@@ -0,0 +1,4 @@
from p1 import m1
m1()
#<ref>

View File

@@ -0,0 +1 @@
import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('p1',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('p1',types.ModuleType('p1')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p)

View File

@@ -0,0 +1,2 @@
def foo():
pass

View File

@@ -0,0 +1,4 @@
import p1
p1
#<ref>

View File

@@ -0,0 +1 @@
import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('p1',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('p1',types.ModuleType('p1')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p)

View File

@@ -0,0 +1,2 @@
def foo():
pass

View File

@@ -0,0 +1,2 @@
import p1
#<ref>

View File

@@ -0,0 +1 @@
import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('p1',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('p1',types.ModuleType('p1')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p)

View File

@@ -0,0 +1,2 @@
def foo():
pass

View File

@@ -9,6 +9,7 @@ import com.jetbrains.cython.psi.CythonFunction;
import com.jetbrains.cython.psi.CythonVariable;
import com.jetbrains.python.fixtures.PyResolveTestCase;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyImportedModule;
/**
* @author yole
@@ -348,6 +349,29 @@ public class PyMultiFileResolveTest extends PyResolveTestCase {
assertEquals("x", ((PsiNamedElement)field).getName());
}
// PY-2813
public void testFromNamespacePackageImport() {
assertResolvesTo(PyFunction.class, "foo");
}
// PY-2813
public void testNamespacePackage() {
final PsiElement element = doResolve();
assertInstanceOf(element, PyImportedModule.class);
final PyImportedModule module = (PyImportedModule)element;
assertEquals("p1", module.getImportedPrefix().toString());
}
// PY-2813
public void testNamespacePackageImport() {
assertResolvesTo(PsiDirectory.class, "p1");
}
// PY-2813
public void testFromNamespacePackageImportModule() {
assertResolvesTo(PyFile.class, "m1.py");
}
private void prepareTestDirectory() {
final String testName = getTestName(true);
myFixture.copyDirectoryToProject(testName, "");

View File

@@ -563,4 +563,9 @@ public class PythonCompletionTest extends PyTestCase {
setLanguageLevel(null);
}
}
// PY-2813
public void testFromNamespacePackageImport() {
doMultiFileTest();
}
}

View File

@@ -111,6 +111,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyTestCase {
doMultiFileTest("a.py");
}
// PY-2813
public void testNamespacePackageAttributes() {
doMultiFileTest("a.py");
}
private void doTest() {
myFixture.configureByFile(TEST_DIRECTORY + getTestName(true) + ".py");
myFixture.enableInspections(PyUnresolvedReferencesInspection.class);