From 645bd3a1b27bb3c3a45e557350fb3b81da1ab52a Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Tue, 23 Dec 2014 18:05:48 +0300 Subject: [PATCH] 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. --- .../src/com/jetbrains/python/PyNames.java | 2 ++ .../python/psi/impl/PyClassImpl.java | 36 ++++++++++++++++++- .../overriddenMRO.py | 22 ++++++++++++ .../overriddenMROInAncestors.py | 28 +++++++++++++++ .../PyUnresolvedReferencesInspectionTest.java | 10 ++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMRO.py create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection/overriddenMROInAncestors.py 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() {