Fixed resolve of multiple inherited constructors for new-style MRO (PY-9080)

Integrated a C3 MRO algorithm implementation into PyClass.getAncestorTypes().
This commit is contained in:
Andrey Vlasovskikh
2013-04-18 18:05:03 +04:00
parent cfc8d927bc
commit f8eb0dd7f6
4 changed files with 67 additions and 43 deletions
@@ -122,17 +122,6 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine
@NotNull
List<PyClassLikeType> getAncestorTypes(@NotNull TypeEvalContext context);
/**
* Return the method resolution order list for this class.
* <br/>
* see http://www.python.org/download/releases/2.3/mro/
* <br/>
* <i>Note: the list begins with this class.</i> It ends with the builtin 'object'.
* If class hierarchy is incorrect, e.g. badly looped, assertions may fail in implementation.
* @return list of classes in method resolution order for this class, at least one element long.
*/
@NotNull List<PyClass> getMRO();
/**
* Scan properties in order of definition, until processor returns true for one of them.
* @param processor to check properties
@@ -2,6 +2,7 @@ package com.jetbrains.python.psi.impl;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
@@ -283,30 +284,22 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
return result.toArray(new PyClass[result.size()]);
}
public
@NotNull
List<PyClass> getMRO() {
// see http://www.python.org/download/releases/2.3/mro/ for a muddy explanation.
// see http://hackage.haskell.org/packages/archive/MetaObject/latest/doc/html/src/MO-Util-C3.html#linearize for code to port from.
return mroLinearize(this, Collections.<PyClass>emptyList());
}
private static List<PyClass> mroMerge(List<List<PyClass>> sequences) {
List<PyClass> result = new LinkedList<PyClass>(); // need to insert to 0th position on linearize
private static List<PyClassLikeType> mroMerge(@NotNull List<List<PyClassLikeType>> sequences) {
List<PyClassLikeType> result = new LinkedList<PyClassLikeType>(); // need to insert to 0th position on linearize
while (true) {
// filter blank sequences
List<List<PyClass>> nonBlankSequences = new ArrayList<List<PyClass>>(sequences.size());
for (List<PyClass> item : sequences) {
List<List<PyClassLikeType>> nonBlankSequences = new ArrayList<List<PyClassLikeType>>(sequences.size());
for (List<PyClassLikeType> item : sequences) {
if (item.size() > 0) nonBlankSequences.add(item);
}
if (nonBlankSequences.isEmpty()) return result;
// find a clean head
PyClass head = null; // to keep compiler happy; really head is assigned in the loop at least once.
for (List<PyClass> seq : nonBlankSequences) {
PyClassLikeType head = null; // to keep compiler happy; really head is assigned in the loop at least once.
for (List<PyClassLikeType> seq : nonBlankSequences) {
head = seq.get(0);
boolean head_in_tails = false;
for (List<PyClass> tail_seq : nonBlankSequences) {
for (List<PyClassLikeType> tail_seq : nonBlankSequences) {
if (tail_seq.indexOf(head) > 0) { // -1 is not found, 0 is head, >0 is tail.
head_in_tails = true;
break;
@@ -319,31 +312,36 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
head = null; // as a signal
}
}
assert head != null : "Inconsistent hierarchy!"; // TODO: better diagnostics?
// our head is clean;
result.add(head);
// remove it from heads of other sequences
for (List<PyClass> seq : nonBlankSequences) {
if (seq.get(0) == head) seq.remove(0);
for (List<PyClassLikeType> seq : nonBlankSequences) {
if (Comparing.equal(seq.get(0), head)) seq.remove(0);
}
} // we either return inside the loop or die by assertion
}
private static List<PyClass> mroLinearize(PyClass cls, List<PyClass> seen) {
assert (seen.indexOf(cls) < 0) : "Circular import structure on " + PyUtil.nvl(cls);
PyClass[] bases = cls.getSuperClasses();
List<List<PyClass>> lins = new ArrayList<List<PyClass>>(bases.length * 2);
ArrayList<PyClass> new_seen = new ArrayList<PyClass>(seen.size() + 1);
new_seen.add(cls);
for (PyClass base : bases) {
List<PyClass> lin = mroLinearize(base, new_seen);
if (!lin.isEmpty()) lins.add(lin);
@NotNull
private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, @NotNull List<PyClassLikeType> seen, boolean addThisType,
@NotNull TypeEvalContext context) {
assert (seen.indexOf(type) < 0) : "Circular import structure on " + PyUtil.nvl(type);
final List<PyClassLikeType> bases = type.getSuperClassTypes(context);
List<List<PyClassLikeType>> lins = new ArrayList<List<PyClassLikeType>>(bases.size() * 2);
ArrayList<PyClassLikeType> new_seen = new ArrayList<PyClassLikeType>(seen.size() + 1);
new_seen.add(type);
for (PyClassLikeType base : bases) {
if (base != null) {
List<PyClassLikeType> lin = mroLinearize(base, new_seen, true, context);
if (!lin.isEmpty()) lins.add(lin);
}
}
for (PyClass base : bases) {
lins.add(new SmartList<PyClass>(base));
for (PyClassLikeType base : bases) {
lins.add(new SmartList<PyClassLikeType>(base));
}
List<PyClassLikeType> result = mroMerge(lins);
if (addThisType) {
result.add(0, type);
}
List<PyClass> result = mroMerge(lins);
result.add(0, cls);
return result;
}
@@ -891,7 +889,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
final PyClass objClass = PyBuiltinCache.getInstance(this).getClass("object");
if (this == objClass) return true; // a rare but possible case
if (hasNewStyleMetaClass(this)) return true;
for (PyClassLikeType type : getAncestorTypes(TypeEvalContext.fastStubOnly(null))) {
for (PyClassLikeType type : getOldStyleAncestorTypes(TypeEvalContext.fastStubOnly(null))) {
if (type == null) {
// unknown, assume new-style class
return true;
@@ -1057,6 +1055,20 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
@NotNull
@Override
public List<PyClassLikeType> getAncestorTypes(@NotNull TypeEvalContext context) {
return isNewStyleClass() ? getMROAncestorTypes(context) : getOldStyleAncestorTypes(context);
}
@NotNull
private List<PyClassLikeType> getMROAncestorTypes(@NotNull TypeEvalContext context) {
final PyType thisType = context.getType(this);
if (thisType instanceof PyClassLikeType) {
return mroLinearize((PyClassLikeType)thisType, Collections.<PyClassLikeType>emptyList(), false, context);
}
return Collections.emptyList();
}
@NotNull
private List<PyClassLikeType> getOldStyleAncestorTypes(@NotNull TypeEvalContext context) {
final List<PyClassLikeType> results = new ArrayList<PyClassLikeType>();
final List<PyClassLikeType> toProcess = new ArrayList<PyClassLikeType>();
final Set<PyClassLikeType> seen = new HashSet<PyClassLikeType>();
@@ -0,0 +1,18 @@
class A(object):
pass
class B(object):
def __init__(self, *args):
pass
class C(B):
pass
class D(A, C):
pass
D(42)
@@ -98,6 +98,11 @@ public class PyArgumentListInspectionTest extends PyTestCase {
doTest();
}
// PY-9080
public void testMultipleInheritedConstructorsMRO() {
doTest();
}
public void testPy3k() {
PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.PYTHON30);
try {