mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 04:51:24 +07:00
Fixed unresolved references for namespace packages (PY-2813)
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
from p1 import m1
|
||||
@@ -0,0 +1 @@
|
||||
from p1 import <caret>
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
pass
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
pass
|
||||
@@ -0,0 +1,4 @@
|
||||
from p1.m1 import foo
|
||||
|
||||
foo()
|
||||
#<ref>
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
pass
|
||||
@@ -0,0 +1,4 @@
|
||||
from p1 import m1
|
||||
|
||||
m1()
|
||||
#<ref>
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
pass
|
||||
@@ -0,0 +1,4 @@
|
||||
import p1
|
||||
|
||||
p1
|
||||
#<ref>
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
import p1
|
||||
#<ref>
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
pass
|
||||
@@ -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, "");
|
||||
|
||||
@@ -563,4 +563,9 @@ public class PythonCompletionTest extends PyTestCase {
|
||||
setLanguageLevel(null);
|
||||
}
|
||||
}
|
||||
|
||||
// PY-2813
|
||||
public void testFromNamespacePackageImport() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user