mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
If mro() is overridden in metaclass, use normal MRO + unresolved ancestor (PY-11401)
We cannot evaluate the result of an overridden mro() method, so we don't know the actual MRO chain. Let's assume that MRO is almost the same as without this override and add an element of uncertainty by appending a fake unresolved ancestor type to the MRO chain. Doing so results in, for example, the unresolved references inspection ignoring unresolved references for such a class.
This commit is contained in:
@@ -503,4 +503,6 @@ public class PyNames {
|
||||
public static final ImmutableSet<String> METHOD_SPECIAL_ATTRIBUTES = ImmutableSet.of("__func__", "__self__");
|
||||
|
||||
public static final ImmutableSet<String> LEGACY_METHOD_SPECIAL_ATTRIBUTES = ImmutableSet.of("im_func", "im_self", "im_class");
|
||||
|
||||
public static final String MRO = "mro";
|
||||
}
|
||||
|
||||
@@ -1330,13 +1330,47 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
private List<PyClassLikeType> getMROAncestorTypes(@NotNull TypeEvalContext context) throws MROException {
|
||||
final PyType thisType = context.getType(this);
|
||||
if (thisType instanceof PyClassLikeType) {
|
||||
return mroLinearize((PyClassLikeType)thisType, new HashSet<PyClassLikeType>(), false, context);
|
||||
final PyClassLikeType thisClassLikeType = (PyClassLikeType)thisType;
|
||||
final List<PyClassLikeType> ancestorTypes = mroLinearize(thisClassLikeType, new HashSet<PyClassLikeType>(), false, context);
|
||||
if (isOverriddenMRO(ancestorTypes, context)) {
|
||||
ancestorTypes.add(null);
|
||||
}
|
||||
return ancestorTypes;
|
||||
}
|
||||
else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOverriddenMRO(@NotNull List<PyClassLikeType> ancestorTypes, @NotNull TypeEvalContext context) {
|
||||
final List<PyClass> classes = new ArrayList<PyClass>();
|
||||
classes.add(this);
|
||||
for (PyClassLikeType ancestorType : ancestorTypes) {
|
||||
if (ancestorType instanceof PyClassType) {
|
||||
final PyClassType classType = (PyClassType)ancestorType;
|
||||
classes.add(classType.getPyClass());
|
||||
}
|
||||
}
|
||||
|
||||
final PyClass typeClass = PyBuiltinCache.getInstance(this).getClass("type");
|
||||
|
||||
for (PyClass cls : classes) {
|
||||
final PyType metaClassType = cls.getMetaClassType(context);
|
||||
if (metaClassType instanceof PyClassType) {
|
||||
final PyClass metaClass = ((PyClassType)metaClassType).getPyClass();
|
||||
final PyFunction mroMethod = metaClass.findMethodByName(PyNames.MRO, true);
|
||||
if (mroMethod != null) {
|
||||
final PyClass mroClass = mroMethod.getContainingClass();
|
||||
if (mroClass != null && mroClass != typeClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<PyClassLikeType> getOldStyleAncestorTypes(@NotNull TypeEvalContext context) {
|
||||
final List<PyClassLikeType> results = new ArrayList<PyClassLikeType>();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
class A(object):
|
||||
def foo(self):
|
||||
return 0
|
||||
|
||||
|
||||
class B(object):
|
||||
def bar(self):
|
||||
return 0
|
||||
|
||||
|
||||
class MyMeta(type):
|
||||
def mro(cls):
|
||||
return A, B
|
||||
|
||||
|
||||
class C(B):
|
||||
__metaclass__ = MyMeta
|
||||
|
||||
|
||||
c = C()
|
||||
print(c.foo().lower()) # pass
|
||||
print(c.bar().<warning descr="Unresolved attribute reference 'lower' for class 'int'">lower</warning>())
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
class A(object):
|
||||
def foo(self):
|
||||
return 0
|
||||
|
||||
|
||||
class MyMeta(type):
|
||||
def mro(cls):
|
||||
return A, B
|
||||
|
||||
|
||||
class MyMeta2(MyMeta):
|
||||
pass
|
||||
|
||||
|
||||
class B(object):
|
||||
__metaclass__ = MyMeta2
|
||||
|
||||
def bar(self):
|
||||
return 0
|
||||
|
||||
|
||||
class C(B):
|
||||
pass
|
||||
|
||||
|
||||
c = C()
|
||||
print(c.foo().lower()) # pass
|
||||
print(c.bar().<warning descr="Unresolved attribute reference 'lower' for class 'int'">lower</warning>())
|
||||
+10
@@ -450,6 +450,16 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-11401
|
||||
public void testOverriddenMRO() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-11401
|
||||
public void testOverriddenMROInAncestors() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user