diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 30587ef1d5d1..5532fa37da30 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -63,6 +63,12 @@ import static com.intellij.openapi.util.text.StringUtil.notNullize; * @author yole */ public class PyClassImpl extends PyBaseElementImpl implements PyClass { + public static class MROException extends Exception { + public MROException(String s) { + super(s); + } + } + public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0]; private List myInstanceAttributes; @@ -80,7 +86,28 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla @Nullable @Override public CachedValueProvider.Result> compute(@NotNull TypeEvalContext context) { - final List ancestorTypes = isNewStyleClass() ? getMROAncestorTypes(context) : getOldStyleAncestorTypes(context); + List ancestorTypes; + if (isNewStyleClass()) { + try { + ancestorTypes = getMROAncestorTypes(context); + } + catch (MROException e) { + ancestorTypes = getOldStyleAncestorTypes(context); + boolean hasUnresolvedAncestorTypes = false; + for (PyClassLikeType type : ancestorTypes) { + if (type == null) { + hasUnresolvedAncestorTypes = true; + break; + } + } + if (!hasUnresolvedAncestorTypes) { + ancestorTypes = Collections.singletonList(null); + } + } + } + else { + ancestorTypes = getOldStyleAncestorTypes(context); + } return CachedValueProvider.Result.create(ancestorTypes, PsiModificationTracker.MODIFICATION_COUNT); } } @@ -321,7 +348,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla } @NotNull - private static List mroMerge(@NotNull List> sequences) { + private static List mroMerge(@NotNull List> sequences) throws MROException { List result = new LinkedList(); // need to insert to 0th position on linearize while (true) { // filter blank sequences @@ -357,7 +384,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla } if (!found) { // Inconsistent hierarchy results in TypeError - throw new IllegalStateException("Inconsistent class hierarchy"); + throw new MROException("Inconsistent class hierarchy"); } // our head is clean; result.add(head); @@ -374,9 +401,9 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla @NotNull private static List mroLinearize(@NotNull PyClassLikeType type, @NotNull Set seen, boolean addThisType, - @NotNull TypeEvalContext context) { + @NotNull TypeEvalContext context) throws MROException { if (seen.contains(type)) { - throw new IllegalStateException("Circular class inheritance"); + throw new MROException("Circular class inheritance"); } final List bases = type.getSuperClassTypes(context); List> lines = new ArrayList>(); @@ -1300,16 +1327,14 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla } @NotNull - private List getMROAncestorTypes(@NotNull TypeEvalContext context) { + private List getMROAncestorTypes(@NotNull TypeEvalContext context) throws MROException { final PyType thisType = context.getType(this); if (thisType instanceof PyClassLikeType) { - try { - return mroLinearize((PyClassLikeType)thisType, new HashSet(), false, context); - } - catch (IllegalStateException ignored) { - } + return mroLinearize((PyClassLikeType)thisType, new HashSet(), false, context); + } + else { + return Collections.emptyList(); } - return Collections.emptyList(); } @NotNull diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/fallbackToOldStyleMROIfUnresolvedAncestorsAndC3Fails.py b/python/testData/inspections/PyUnresolvedReferencesInspection/fallbackToOldStyleMROIfUnresolvedAncestorsAndC3Fails.py new file mode 100644 index 000000000000..edd8344dfae9 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/fallbackToOldStyleMROIfUnresolvedAncestorsAndC3Fails.py @@ -0,0 +1,22 @@ +class X(Unresolved): + pass + + +class Y(Unresolved): + pass + + +class A(X, Y): + def foo(self): + pass + + +class B(Y, X): + pass + + +class C(A, B): # we don't know whether MRO is OK or not + pass + + +print(C.foo) # pass diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/noUnresolvedReferencesForClassesWithBadMRO.py b/python/testData/inspections/PyUnresolvedReferencesInspection/noUnresolvedReferencesForClassesWithBadMRO.py new file mode 100644 index 000000000000..600ec8e33a53 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/noUnresolvedReferencesForClassesWithBadMRO.py @@ -0,0 +1,26 @@ +class O(object): + pass + + +class X(O): + pass + + +class Y(O): + pass + + +class A(X, Y): + def foo(self): + pass + + +class B(Y, X): + pass + + +class C(A, B): # bad MRO + pass + + +print(C.foo) # pass diff --git a/python/testData/resolve/ResolveAttributesUsingOldStyleMROWhenUnresolvedAncestorsAndC3Fails.py b/python/testData/resolve/ResolveAttributesUsingOldStyleMROWhenUnresolvedAncestorsAndC3Fails.py new file mode 100644 index 000000000000..c279d67c9a4c --- /dev/null +++ b/python/testData/resolve/ResolveAttributesUsingOldStyleMROWhenUnresolvedAncestorsAndC3Fails.py @@ -0,0 +1,23 @@ +class X(Unresolved): + pass + + +class Y(Unresolved): + pass + + +class A(X, Y): + def foo(self): + pass + + +class B(Y, X): + pass + + +class C(A, B): # we don't know whether MRO is OK or not + pass + + +print(C.foo) +# diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index 91a8955938cf..52089593aec6 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -567,4 +567,9 @@ public class PyResolveTest extends PyResolveTestCase { PyTargetExpression xyzzy = assertResolvesTo(PyTargetExpression.class, "xyzzy"); assertEquals("__init__", PsiTreeUtil.getParentOfType(xyzzy, PyFunction.class).getName()); } + + // PY-11401 + public void testResolveAttributesUsingOldStyleMROWhenUnresolvedAncestorsAndC3Fails() { + assertResolvesTo(PyFunction.class, "foo"); + } } diff --git a/python/testSrc/com/jetbrains/python/codeInsight/PyClassMROTest.java b/python/testSrc/com/jetbrains/python/codeInsight/PyClassMROTest.java index 2f500d573511..a6a2adc488c5 100644 --- a/python/testSrc/com/jetbrains/python/codeInsight/PyClassMROTest.java +++ b/python/testSrc/com/jetbrains/python/codeInsight/PyClassMROTest.java @@ -35,7 +35,7 @@ public class PyClassMROTest extends PyTestCase { // TypeError in Python public void testMROConflict() { - assertMRO(getClass("C")); + assertMRO(getClass("C"), "unknown"); } public void testCircularInheritance() { @@ -43,7 +43,7 @@ public class PyClassMROTest extends PyTestCase { myFixture.configureByFiles(getPath(testName), getPath(testName + "2")); final PyClass cls = myFixture.findElementByText("Foo", PyClass.class); assertNotNull(cls); - assertMRO(cls); + assertMRO(cls, "unknown"); } public void testExampleFromDoc1() { @@ -55,7 +55,7 @@ public class PyClassMROTest extends PyTestCase { } public void testExampleFromDoc3() { - assertMRO(getClass("G")); + assertMRO(getClass("G"), "unknown"); } public void testExampleFromDoc4() { @@ -71,7 +71,7 @@ public class PyClassMROTest extends PyTestCase { assertMRO(getClass("H"), "E", "F", "B", "G", "C", "D", "A", "object"); } - // PY-11932 + // PY-11401 public void testUnresolvedClassesImpossibleToBuildMRO() { assertMRO(getClass("ObjectManager"), "CopyContainer", "unknown", "Navigation", "unknown", "Tabs", "unknown", "unknown", "unknown", "Collection", "Resource", diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index b367fcb8089f..d67766e31468 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -440,6 +440,16 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doMultiFileTest("p1/__init__.py"); } + // PY-11401 + public void testNoUnresolvedReferencesForClassesWithBadMRO() { + doTest(); + } + + // PY-11401 + public void testFallbackToOldStyleMROIfUnresolvedAncestorsAndC3Fails() { + doTest(); + } + @NotNull @Override protected Class getInspectionClass() {