Memoize result of MRO resolution

This commit is contained in:
Mikhail Golubev
2014-10-16 21:47:51 +04:00
parent 595f6a9259
commit 3c7e51dcd7
3 changed files with 173 additions and 24 deletions
@@ -62,6 +62,7 @@ import static com.intellij.openapi.util.text.StringUtil.notNullize;
*/
public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyClass {
public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0];
private static final Object EVALUATING = new Object();
private List<PyTargetExpression> myInstanceAttributes;
private final NotNullLazyValue<CachedValue<Boolean>> myNewStyle = new NotNullLazyValue<CachedValue<Boolean>>() {
@@ -323,7 +324,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
List<PyClassLikeType> result = new LinkedList<PyClassLikeType>(); // need to insert to 0th position on linearize
while (true) {
// filter blank sequences
List<List<PyClassLikeType>> nonBlankSequences = new ArrayList<List<PyClassLikeType>>(sequences.size());
final List<List<PyClassLikeType>> nonBlankSequences = new ArrayList<List<PyClassLikeType>>(sequences.size());
for (List<PyClassLikeType> item : sequences) {
if (item.size() > 0) nonBlankSequences.add(item);
}
@@ -333,14 +334,14 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
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<PyClassLikeType> tail_seq : nonBlankSequences) {
if (tail_seq.indexOf(head) > 0) { // -1 is not found, 0 is head, >0 is tail.
head_in_tails = true;
boolean headInTails = false;
for (List<PyClassLikeType> tailSeq : nonBlankSequences) {
if (tailSeq.indexOf(head) > 0) { // -1 is not found, 0 is head, >0 is tail.
headInTails = true;
break;
}
}
if (!head_in_tails) {
if (!headInTails) {
found = true;
break;
}
@@ -363,28 +364,46 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
} // we either return inside the loop or die by assertion
}
private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, boolean addThisType, @NotNull TypeEvalContext context) {
return mroLinearize(type, addThisType, context, new HashMap<PyClassLikeType, Object>());
}
@NotNull
private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, @NotNull Set<PyClassLikeType> seen, boolean addThisType,
@NotNull TypeEvalContext context) {
if (seen.contains(type)) {
private static List<PyClassLikeType> mroLinearize(@NotNull PyClassLikeType type, boolean addThisType,
@NotNull TypeEvalContext context,
@NotNull Map<PyClassLikeType, Object> cache) {
final Object computed = cache.get(type);
if (computed == EVALUATING) {
throw new IllegalStateException("Circular class inheritance");
}
final List<PyClassLikeType> bases = type.getSuperClassTypes(context);
List<List<PyClassLikeType>> lines = new ArrayList<List<PyClassLikeType>>();
for (PyClassLikeType base : bases) {
if (base != null) {
final Set<PyClassLikeType> newSeen = new HashSet<PyClassLikeType>(seen);
newSeen.add(type);
List<PyClassLikeType> lin = mroLinearize(base, newSeen, true, context);
if (!lin.isEmpty()) lines.add(lin);
if (computed != null) {
//noinspection unchecked
return (List<PyClassLikeType>)computed;
}
cache.put(type, EVALUATING);
List<PyClassLikeType> result = null;
try {
final List<PyClassLikeType> bases = type.getSuperClassTypes(context);
final List<List<PyClassLikeType>> lines = new ArrayList<List<PyClassLikeType>>();
for (PyClassLikeType base : bases) {
if (base != null) {
final List<PyClassLikeType> baseClassMRO = mroLinearize(base, true, context, cache);
if (!baseClassMRO.isEmpty()) {
lines.add(baseClassMRO);
}
}
}
if (!bases.isEmpty()) {
lines.add(bases);
}
result = mroMerge(lines);
if (addThisType) {
result.add(0, type);
}
}
if (!bases.isEmpty()) {
lines.add(bases);
}
List<PyClassLikeType> result = mroMerge(lines);
if (addThisType) {
result.add(0, type);
finally {
cache.put(type, result);
}
return result;
}
@@ -1280,7 +1299,7 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
final PyType thisType = context.getType(this);
if (thisType instanceof PyClassLikeType) {
try {
return mroLinearize((PyClassLikeType)thisType, new HashSet<PyClassLikeType>(), false, context);
return mroLinearize((PyClassLikeType)thisType, false, context);
}
catch (IllegalStateException ignored) {
}
@@ -0,0 +1,117 @@
class Class001(object):
unique_attr = 1
class Class002(Class001):
unique_attr = 2
class Class003(Class002, Class001):
unique_attr = 3
class Class004(Class003, Class002, Class001):
unique_attr = 4
class Class005(Class004, Class003, Class002):
unique_attr = 5
class Class006(Class005, Class004, Class003):
unique_attr = 6
class Class007(Class006, Class005, Class004):
unique_attr = 7
class Class008(Class007, Class006, Class005):
unique_attr = 8
class Class009(Class008, Class007, Class006):
unique_attr = 9
class Class010(Class009, Class008, Class007):
unique_attr = 10
class Class011(Class010, Class009, Class008):
unique_attr = 11
class Class012(Class011, Class010, Class009):
unique_attr = 12
class Class013(Class012, Class011, Class010):
unique_attr = 13
class Class014(Class013, Class012, Class011):
unique_attr = 14
class Class015(Class014, Class013, Class012):
unique_attr = 15
class Class016(Class015, Class014, Class013):
unique_attr = 16
class Class017(Class016, Class015, Class014):
unique_attr = 17
class Class018(Class017, Class016, Class015):
unique_attr = 18
class Class019(Class018, Class017, Class016):
unique_attr = 19
class Class020(Class019, Class018, Class017):
unique_attr = 20
class Class021(Class020, Class019, Class018):
unique_attr = 21
class Class022(Class021, Class020, Class019):
unique_attr = 22
class Class023(Class022, Class021, Class020):
unique_attr = 23
class Class024(Class023, Class022, Class021):
unique_attr = 24
class Class025(Class024, Class023, Class022):
unique_attr = 25
class Class026(Class025, Class024, Class023):
unique_attr = 26
class Class027(Class026, Class025, Class024):
unique_attr = 27
class Class028(Class027, Class026, Class025):
unique_attr = 28
class Class029(Class028, Class027, Class026):
unique_attr = 29
class Class030(Class029, Class028, Class027):
unique_attr = 30
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.codeInsight;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.types.PyClassLikeType;
@@ -71,6 +72,18 @@ public class PyClassMROTest extends PyTestCase {
assertMRO(getClass("H"), "E", "F", "B", "G", "C", "D", "A", "object");
}
public void testTangledInheritance() throws Exception {
final List<String> expectedMRO = new ArrayList<String>();
for (int i = 29; i >= 1; i--) {
expectedMRO.add(String.format("Class%03d", i));
}
expectedMRO.add("object");
final long startTime = System.currentTimeMillis();
assertMRO(getClass("Class030"), ArrayUtil.toStringArray(expectedMRO));
final long elapsed = System.currentTimeMillis() - startTime;
assertTrue("Calculation of MRO takes too much time: " + elapsed + " ms", elapsed < 5000);
}
public void assertMRO(@NotNull PyClass cls, @NotNull String... mro) {
final List<PyClassLikeType> types = cls.getAncestorTypes(TypeEvalContext.codeInsightFallback());
final List<String> classNames = new ArrayList<String>();