Fall back to old-style ancestors if C3 MRO fails and there are unresolved ancestors (PY-11401)

If the C3 MRO algorithm fails and the old-style ancestors algorithm
tells that there are unresolved ancestors, we return a single 'null'
ancestor as a sign that we don't know who the ancestors really are.

If there are no unresolved ancestors, then we fall back to the
old-style ancestors algorithm in order to make resolve work. A future
inspection for detecting incorrect MRO may warn the user about this
situation.
This commit is contained in:
Andrey Vlasovskikh
2014-12-23 17:14:23 +03:00
parent b37b0440d0
commit 19e9c38a86
7 changed files with 127 additions and 16 deletions
@@ -63,6 +63,12 @@ import static com.intellij.openapi.util.text.StringUtil.notNullize;
* @author yole
*/
public class PyClassImpl extends PyBaseElementImpl<PyClassStub> 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<PyTargetExpression> myInstanceAttributes;
@@ -80,7 +86,28 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
@Nullable
@Override
public CachedValueProvider.Result<List<PyClassLikeType>> compute(@NotNull TypeEvalContext context) {
final List<PyClassLikeType> ancestorTypes = isNewStyleClass() ? getMROAncestorTypes(context) : getOldStyleAncestorTypes(context);
List<PyClassLikeType> 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<PyClassStub> implements PyCla
}
@NotNull
private static List<PyClassLikeType> mroMerge(@NotNull List<List<PyClassLikeType>> sequences) {
private static List<PyClassLikeType> mroMerge(@NotNull List<List<PyClassLikeType>> sequences) throws MROException {
List<PyClassLikeType> result = new LinkedList<PyClassLikeType>(); // need to insert to 0th position on linearize
while (true) {
// filter blank sequences
@@ -357,7 +384,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> 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<PyClassStub> implements PyCla
@NotNull
private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, @NotNull Set<PyClassLikeType> 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<PyClassLikeType> bases = type.getSuperClassTypes(context);
List<List<PyClassLikeType>> lines = new ArrayList<List<PyClassLikeType>>();
@@ -1300,16 +1327,14 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
}
@NotNull
private List<PyClassLikeType> getMROAncestorTypes(@NotNull TypeEvalContext context) {
private List<PyClassLikeType> getMROAncestorTypes(@NotNull TypeEvalContext context) throws MROException {
final PyType thisType = context.getType(this);
if (thisType instanceof PyClassLikeType) {
try {
return mroLinearize((PyClassLikeType)thisType, new HashSet<PyClassLikeType>(), false, context);
}
catch (IllegalStateException ignored) {
}
return mroLinearize((PyClassLikeType)thisType, new HashSet<PyClassLikeType>(), false, context);
}
else {
return Collections.emptyList();
}
return Collections.emptyList();
}
@NotNull
@@ -0,0 +1,22 @@
class X(<error descr="Unresolved reference 'Unresolved'">Unresolved</error>):
pass
class Y(<error descr="Unresolved reference 'Unresolved'">Unresolved</error>):
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
@@ -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
@@ -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)
# <ref>
@@ -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");
}
}
@@ -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",
@@ -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<? extends PyInspection> getInspectionClass() {