mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Support module with __getattr__ while resolving and type checking (PY-28017)
This commit is contained in:
+63
-32
@@ -51,6 +51,7 @@ import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.impl.PyImportStatementNavigator;
|
||||
import com.jetbrains.python.psi.impl.PyImportedModule;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.impl.references.PyFromImportNameReference;
|
||||
import com.jetbrains.python.psi.impl.references.PyImportReference;
|
||||
import com.jetbrains.python.psi.impl.references.PyOperatorReference;
|
||||
import com.jetbrains.python.psi.resolve.ImportedResolveResult;
|
||||
@@ -557,43 +558,43 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
|
||||
if (PyNames.COMPARISON_OPERATORS.contains(expr.getReferencedName())) {
|
||||
return;
|
||||
}
|
||||
final PyExpression qualifier = expr.getQualifier();
|
||||
if (qualifier != null) {
|
||||
PyType type = myTypeEvalContext.getType(qualifier);
|
||||
if (type != null) {
|
||||
if (ignoreUnresolvedMemberForType(type, reference, refName)) {
|
||||
return;
|
||||
}
|
||||
addCreateMemberFromUsageFixes(type, reference, refText, actions);
|
||||
if (type instanceof PyClassType) {
|
||||
final PyClassType classType = (PyClassType)type;
|
||||
if (reference instanceof PyOperatorReference) {
|
||||
String className = type.getName();
|
||||
if (classType.isDefinition()) {
|
||||
final PyClassLikeType metaClassType = classType.getMetaClassType(myTypeEvalContext, true);
|
||||
if (metaClassType != null) {
|
||||
className = metaClassType.getName();
|
||||
}
|
||||
}
|
||||
final PyExpression qualifier = getReferenceQualifier(reference);
|
||||
if (qualifier != null) {
|
||||
final PyType type = myTypeEvalContext.getType(qualifier);
|
||||
if (type != null) {
|
||||
if (ignoreUnresolvedMemberForType(type, reference, refName)) {
|
||||
return;
|
||||
}
|
||||
addCreateMemberFromUsageFixes(type, reference, refText, actions);
|
||||
if (type instanceof PyClassType) {
|
||||
final PyClassType classType = (PyClassType)type;
|
||||
if (reference instanceof PyOperatorReference) {
|
||||
String className = type.getName();
|
||||
if (classType.isDefinition()) {
|
||||
final PyClassLikeType metaClassType = classType.getMetaClassType(myTypeEvalContext, true);
|
||||
if (metaClassType != null) {
|
||||
className = metaClassType.getName();
|
||||
}
|
||||
description = PyBundle.message("INSP.unresolved.operator.ref",
|
||||
className, refName,
|
||||
((PyOperatorReference)reference).getReadableOperatorName());
|
||||
}
|
||||
else {
|
||||
final List<String> slots = classType.getPyClass().getOwnSlots();
|
||||
|
||||
if (slots != null && slots.contains(refName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
description = PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", refText, type.getName());
|
||||
}
|
||||
markedQualified = true;
|
||||
description = PyBundle.message("INSP.unresolved.operator.ref",
|
||||
className, refName,
|
||||
((PyOperatorReference)reference).getReadableOperatorName());
|
||||
}
|
||||
else {
|
||||
description = PyBundle.message("INSP.cannot.find.$0.in.$1", refText, type.getName());
|
||||
markedQualified = true;
|
||||
final List<String> slots = classType.getPyClass().getOwnSlots();
|
||||
|
||||
if (slots != null && slots.contains(refName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
description = PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", refText, type.getName());
|
||||
}
|
||||
markedQualified = true;
|
||||
}
|
||||
else {
|
||||
description = PyBundle.message("INSP.cannot.find.$0.in.$1", refText, type.getName());
|
||||
markedQualified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -657,6 +658,30 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
|
||||
registerProblem(node, description, hl_type, null, rangeInElement, actions.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyExpression getReferenceQualifier(@NotNull PsiReference reference) {
|
||||
final PsiElement element = reference.getElement();
|
||||
|
||||
if (element instanceof PyQualifiedExpression) {
|
||||
final PyExpression qualifier = ((PyQualifiedExpression)element).getQualifier();
|
||||
if (qualifier != null) {
|
||||
return qualifier;
|
||||
}
|
||||
}
|
||||
|
||||
if (reference instanceof PyFromImportNameReference) {
|
||||
final PyFromImportStatement statement = PsiTreeUtil.getParentOfType(element, PyFromImportStatement.class);
|
||||
if (statement != null) {
|
||||
final PyReferenceExpression source = statement.getImportSource();
|
||||
if (source != null) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void addInstallPackageAction(List<LocalQuickFix> actions, String packageName, Module module, Sdk sdk) {
|
||||
final List<PyRequirement> requirements = Collections.singletonList(new PyRequirement(packageName));
|
||||
final String name = "Install package " + packageName;
|
||||
@@ -808,6 +833,12 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
|
||||
if (type instanceof PyUnionType) {
|
||||
return ContainerUtil.exists(((PyUnionType)type).getMembers(), member -> ignoreUnresolvedMemberForType(member, reference, name));
|
||||
}
|
||||
if (type instanceof PyModuleType) {
|
||||
final PyFile module = ((PyModuleType)type).getModule();
|
||||
if (module.getLanguageLevel().isAtLeast(LanguageLevel.PYTHON37)) {
|
||||
return PyTypeChecker.definesGetAttr(module, myTypeEvalContext);
|
||||
}
|
||||
}
|
||||
for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) {
|
||||
if (extension.ignoreUnresolvedMember(type, name, myTypeEvalContext)) {
|
||||
return true;
|
||||
|
||||
@@ -247,6 +247,12 @@ public class PyTypeChecker {
|
||||
final Set<String> actualAttributes = actualClassType.getMemberNames(true, context);
|
||||
return actualAttributes.containsAll(((PyStructuralType)expected).getAttributeNames());
|
||||
}
|
||||
if (expected instanceof PyStructuralType && actual instanceof PyModuleType) {
|
||||
final PyFile module = ((PyModuleType)actual).getModule();
|
||||
if (module.getLanguageLevel().isAtLeast(LanguageLevel.PYTHON37) && definesGetAttr(module, context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (expected instanceof PyStructuralType) {
|
||||
final Set<String> expectedAttributes = ((PyStructuralType)expected).getAttributeNames();
|
||||
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
|
||||
@@ -702,29 +708,36 @@ public class PyTypeChecker {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean overridesGetAttr(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
|
||||
PsiElement method = resolveClassMember(cls, PyNames.GETATTR, context);
|
||||
if (method != null) {
|
||||
return true;
|
||||
public static boolean definesGetAttr(@NotNull PyFile file, @NotNull TypeEvalContext context) {
|
||||
if (file instanceof PyTypedElement) {
|
||||
final PyType type = context.getType((PyTypedElement)file);
|
||||
if (type != null) {
|
||||
return resolveTypeMember(type, PyNames.GETATTR, context) != null;
|
||||
}
|
||||
}
|
||||
method = resolveClassMember(cls, PyNames.GETATTRIBUTE, context);
|
||||
if (method != null && !PyBuiltinCache.getInstance(cls).isBuiltin(method)) {
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean overridesGetAttr(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
|
||||
final PyType type = context.getType(cls);
|
||||
if (type != null) {
|
||||
if (resolveTypeMember(type, PyNames.GETATTR, context) != null) {
|
||||
return true;
|
||||
}
|
||||
final PsiElement method = resolveTypeMember(type, PyNames.GETATTRIBUTE, context);
|
||||
if (method != null && !PyBuiltinCache.getInstance(cls).isBuiltin(method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiElement resolveClassMember(@NotNull PyClass cls, @NotNull String name, @NotNull TypeEvalContext context) {
|
||||
final PyType type = context.getType(cls);
|
||||
if (type != null) {
|
||||
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
|
||||
final List<? extends RatedResolveResult> results = type.resolveMember(name, null, AccessDirection.READ, resolveContext);
|
||||
if (results != null && !results.isEmpty()) {
|
||||
return results.get(0).getElement();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
private static PsiElement resolveTypeMember(@NotNull PyType type, @NotNull String name, @NotNull TypeEvalContext context) {
|
||||
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
|
||||
final List<? extends RatedResolveResult> results = type.resolveMember(name, null, AccessDirection.READ, resolveContext);
|
||||
return !ContainerUtil.isEmpty(results) ? results.get(0).getElement() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import mymod
|
||||
|
||||
def foo(mod):
|
||||
return mod.myfunc
|
||||
|
||||
print(foo(mymod)())
|
||||
@@ -0,0 +1,7 @@
|
||||
def myhiddenfunc():
|
||||
return "ok"
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "myfunc":
|
||||
return myhiddenfunc
|
||||
raise AttributeError
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
from importSource import <error descr="Unresolved reference 'len'">len</error>
|
||||
from importSource import <error descr="Cannot find reference 'len' in '__init__.py'">len</error>
|
||||
|
||||
len()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from mymod import myfunc
|
||||
import mymod
|
||||
|
||||
print(myfunc())
|
||||
print(mymod.myfunc())
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
def myhiddenfunc():
|
||||
return "ok"
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "myfunc":
|
||||
return myhiddenfunc
|
||||
raise AttributeError
|
||||
@@ -1,4 +1,4 @@
|
||||
from library import foo,<error descr="Unresolved reference 'bar'">bar</error>
|
||||
from library import foo,<error descr="Cannot find reference 'bar' in 'library.py'">bar</error>
|
||||
|
||||
|
||||
print(foo)
|
||||
+1
-1
@@ -1 +1 @@
|
||||
<warning descr="Unused import statement">from my_module import <error descr="Unresolved reference 'eggs'">eggs</error></warning>
|
||||
<warning descr="Unused import statement">from my_module import <error descr="Cannot find reference 'eggs' in 'my_module.py'">eggs</error></warning>
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
<warning descr="Unused import statement">from my_module import <error descr="Unresolved reference 'eggs'">eggs</error></warning>
|
||||
<warning descr="Unused import statement">from my_module import <error descr="Unresolved reference 'eggs'">eggs</error></warning>
|
||||
<warning descr="Unused import statement">from my_module import <error descr="Cannot find reference 'eggs' in 'my_module.py'">eggs</error></warning>
|
||||
<warning descr="Unused import statement">from my_module import <error descr="Cannot find reference 'eggs' in 'my_module.py'">eggs</error></warning>
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
<warning descr="Unused import statement">from my_package.my_module import <error descr="Unresolved reference 'eggs'">eggs</error></warning>
|
||||
from my_package.my_module import <error descr="Unresolved reference 'eggs'">eggs</error>
|
||||
<warning descr="Unused import statement">from my_package.my_module import <error descr="Cannot find reference 'eggs' in 'my_module.py'">eggs</error></warning>
|
||||
from my_package.my_module import <error descr="Cannot find reference 'eggs' in 'my_module.py'">eggs</error>
|
||||
|
||||
eggs()
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
from lib1 import m1
|
||||
from lib1.m1 import x
|
||||
from lib1.m1 import <error descr="Unresolved reference 'something'">something</error>
|
||||
from lib1.m1 import <error descr="Cannot find reference 'something' in 'm1.py'">something</error>
|
||||
|
||||
|
||||
print(m1, x, something)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from m1 import <error descr="Unresolved reference 'foo'">foo</error>
|
||||
from m1 import <error descr="Unresolved reference 'bar'">bar</error>
|
||||
from m1 import <error descr="Cannot find reference 'foo' in 'm1.pyi'">foo</error>
|
||||
from m1 import <error descr="Cannot find reference 'bar' in 'm1.pyi'">bar</error>
|
||||
from m1 import bar_imported
|
||||
from m1 import <error descr="Unresolved reference 'm2'">m2</error>
|
||||
from m1 import <error descr="Cannot find reference 'm2' in 'm1.pyi'">m2</error>
|
||||
from m1 import m2_imported
|
||||
|
||||
print(foo, bar, bar_imported, m2, m2_imported)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from .m1 import foo
|
||||
from .m1 import <error descr="Unresolved reference 'bar'">bar</error>
|
||||
from .m1 import <error descr="Cannot find reference 'bar' in 'm1.pyi'">bar</error>
|
||||
|
||||
foo2 = foo
|
||||
bar2 = bar
|
||||
@@ -465,4 +465,9 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase {
|
||||
public void testDefinitionAgainstStructural() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
|
||||
}
|
||||
|
||||
// PY-28017
|
||||
public void testModuleWithGetAttr() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON37, this::doMultiFileTest);
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -660,6 +660,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-28017
|
||||
public void testModuleWithGetAttr() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON37, this::doMultiFileTest);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user