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:
Andrey Vlasovskikh
2014-12-23 18:05:48 +03:00
parent 19e9c38a86
commit 645bd3a1b2
5 changed files with 97 additions and 1 deletions
@@ -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>())
@@ -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>())
@@ -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() {