mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-8936 Infer return type for __get__ descriptor attributes
This fix is useful even if we cannot infer the type. Returning Any instead of the descriptor instance suppresses the false positive error.
This commit is contained in:
@@ -85,6 +85,7 @@ public class PyNames {
|
||||
public static final String NEW = "__new__";
|
||||
public static final String GETATTR = "__getattr__";
|
||||
public static final String GETATTRIBUTE = "__getattribute__";
|
||||
public static final String GET = "__get__";
|
||||
public static final String __CLASS__ = "__class__";
|
||||
public static final String DUNDER_METACLASS = "__metaclass__";
|
||||
public static final String METACLASS = "metaclass";
|
||||
|
||||
@@ -48,6 +48,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* Implements reference expression PSI.
|
||||
*
|
||||
@@ -252,6 +254,10 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
if (qualified && typeFromTargets instanceof PyNoneType) {
|
||||
return null;
|
||||
}
|
||||
final Ref<PyType> descriptorType = getDescriptorType(typeFromTargets, context);
|
||||
if (descriptorType != null) {
|
||||
return descriptorType.get();
|
||||
}
|
||||
return typeFromTargets;
|
||||
}
|
||||
finally {
|
||||
@@ -259,6 +265,24 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Ref<PyType> getDescriptorType(@Nullable PyType typeFromTargets, @NotNull TypeEvalContext context) {
|
||||
if (!isQualified()) return null;
|
||||
final PyClassLikeType targetType = as(typeFromTargets, PyClassLikeType.class);
|
||||
if (targetType == null) return null;
|
||||
final PyResolveContext resolveContext = PyResolveContext.noProperties().withTypeEvalContext(context);
|
||||
final List<? extends RatedResolveResult> members = targetType.resolveMember(PyNames.GET, this, AccessDirection.READ,
|
||||
resolveContext);
|
||||
if (members == null || members.isEmpty()) return null;
|
||||
final List<PyType> types = StreamEx.of(members)
|
||||
.map((result) -> result.getElement())
|
||||
.select(PyCallable.class)
|
||||
.map((callable) -> context.getReturnType(callable))
|
||||
.toList();
|
||||
final PyType type = PyUnionType.union(types);
|
||||
return Ref.create(type);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Ref<PyType> getQualifiedReferenceType(@NotNull TypeEvalContext context) {
|
||||
if (!context.maySwitchToAST(this)) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
class StringDescriptor:
|
||||
def __get__(self, instance, owner):
|
||||
return 'foo'
|
||||
|
||||
|
||||
class AnyDescriptor:
|
||||
def __get__(self, instance, owner) -> Any:
|
||||
return 'bar'
|
||||
|
||||
|
||||
class ListDescriptor:
|
||||
def __get__(self, instance: Any, owner: Any) -> list:
|
||||
return 'baz'
|
||||
|
||||
|
||||
class C:
|
||||
foo = StringDescriptor()
|
||||
bar = AnyDescriptor()
|
||||
baz = ListDescriptor()
|
||||
|
||||
|
||||
# Instance level
|
||||
c = C()
|
||||
c.foo.upper()
|
||||
c.foo.<warning descr="Unresolved attribute reference 'non_existent' for class 'str'">non_existent</warning>()
|
||||
c.bar.upper()
|
||||
c.bar.non_existent()
|
||||
c.baz.append()
|
||||
c.baz.<warning descr="Unresolved attribute reference 'non_existent' for class 'list'">non_existent</warning>()
|
||||
|
||||
|
||||
# Class level
|
||||
C.foo.upper()
|
||||
C.foo.<warning descr="Unresolved attribute reference 'non_existent' for class 'str'">non_existent</warning>()
|
||||
C.bar.upper()
|
||||
C.bar.non_existent()
|
||||
+5
@@ -220,4 +220,9 @@ public class Py3UnresolvedReferencesInspectionTest extends PyTestCase {
|
||||
public void testCallTypeGetAttributeAndSetAttrInInheritor() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-8936
|
||||
public void testDescriptorAttribute() {
|
||||
doTest();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user