mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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
|
||||
|
||||
+22
@@ -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
|
||||
+26
@@ -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
|
||||
+23
@@ -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",
|
||||
|
||||
+10
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user