diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index 21e5be632d59..20a37449454e 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -503,4 +503,6 @@ public class PyNames { public static final ImmutableSet METHOD_SPECIAL_ATTRIBUTES = ImmutableSet.of("__func__", "__self__"); public static final ImmutableSet LEGACY_METHOD_SPECIAL_ATTRIBUTES = ImmutableSet.of("im_func", "im_self", "im_class"); + + public static final String MRO = "mro"; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 5532fa37da30..c03fe346b6e3 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -1330,13 +1330,47 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla private List getMROAncestorTypes(@NotNull TypeEvalContext context) throws MROException { final PyType thisType = context.getType(this); if (thisType instanceof PyClassLikeType) { - return mroLinearize((PyClassLikeType)thisType, new HashSet(), false, context); + final PyClassLikeType thisClassLikeType = (PyClassLikeType)thisType; + final List ancestorTypes = mroLinearize(thisClassLikeType, new HashSet(), false, context); + if (isOverriddenMRO(ancestorTypes, context)) { + ancestorTypes.add(null); + } + return ancestorTypes; } else { return Collections.emptyList(); } } + private boolean isOverriddenMRO(@NotNull List ancestorTypes, @NotNull TypeEvalContext context) { + final List classes = new ArrayList(); + 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 getOldStyleAncestorTypes(@NotNull TypeEvalContext context) { final List results = new ArrayList(); diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMRO.py b/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMRO.py new file mode 100644 index 000000000000..e1f25b3568c8 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMRO.py @@ -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().lower()) diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMROInAncestors.py b/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMROInAncestors.py new file mode 100644 index 000000000000..15cd8ae700d5 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMROInAncestors.py @@ -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().lower()) diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index d67766e31468..327cecabf147 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -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 getInspectionClass() {