Infinite recursion prevented on cyclic class hierarchies

This commit is contained in:
Andrey Breslav
2013-02-11 19:01:35 +04:00
parent 71bad95783
commit 3e0ead494e
5 changed files with 90 additions and 23 deletions
@@ -22,6 +22,7 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Computable;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.*;
@@ -389,30 +390,36 @@ public class LazyClassDescriptor extends ClassDescriptorBase implements LazyDesc
} }
private class LazyClassTypeConstructor implements LazyDescriptor, TypeConstructor { private class LazyClassTypeConstructor implements LazyDescriptor, TypeConstructor {
private final LazyValue<Collection<JetType>> supertypes = resolveSession.getStorageManager().createLazyValue(new Computable<Collection<JetType>>() { private final LazyValue<Collection<JetType>> supertypes = resolveSession.getStorageManager().createLazyValueWithPostCompute(
@Override new Computable<Collection<JetType>>() {
public Collection<JetType> compute() { @Override
if (resolveSession.isClassSpecial(DescriptorUtils.getFQName(LazyClassDescriptor.this))) { public Collection<JetType> compute() {
return Collections.emptyList(); if (resolveSession.isClassSpecial(DescriptorUtils.getFQName(LazyClassDescriptor.this))) {
} return Collections.emptyList();
else { }
JetClassOrObject classOrObject = declarationProvider.getOwnerInfo().getCorrespondingClassOrObject(); else {
if (classOrObject == null) { JetClassOrObject classOrObject = declarationProvider.getOwnerInfo().getCorrespondingClassOrObject();
return Collections.emptyList(); if (classOrObject == null) {
} return Collections.emptyList();
else { }
List<JetType> allSupertypes = resolveSession.getInjector().getDescriptorResolver() else {
.resolveSupertypes(getScopeForClassHeaderResolution(), List<JetType> allSupertypes = resolveSession.getInjector().getDescriptorResolver()
LazyClassDescriptor.this, classOrObject, .resolveSupertypes(getScopeForClassHeaderResolution(),
resolveSession.getTrace()); LazyClassDescriptor.this, classOrObject,
List<JetType> validSupertypes = Lists.newArrayList(Collections2.filter(allSupertypes, VALID_SUPERTYPE)); resolveSession.getTrace());
findAndDisconnectLoopsInTypeHierarchy(validSupertypes); return Lists.newArrayList(Collections2.filter(allSupertypes, VALID_SUPERTYPE));
return validSupertypes; }
}
} }
} },
} new Consumer<Collection<JetType>>() {
}); @Override
public void consume(@NotNull Collection<JetType> supertypes) {
findAndDisconnectLoopsInTypeHierarchy(supertypes);
}
});
private final LazyValue<List<TypeParameterDescriptor>> parameters = resolveSession.getStorageManager().createLazyValue(new Computable<List<TypeParameterDescriptor>>() { private final LazyValue<List<TypeParameterDescriptor>> parameters = resolveSession.getStorageManager().createLazyValue(new Computable<List<TypeParameterDescriptor>>() {
@Override @Override
public List<TypeParameterDescriptor> compute() { public List<TypeParameterDescriptor> compute() {
@@ -440,7 +447,7 @@ public class LazyClassDescriptor extends ClassDescriptorBase implements LazyDesc
return supertypes.get(); return supertypes.get();
} }
private void findAndDisconnectLoopsInTypeHierarchy(List<JetType> supertypes) { private void findAndDisconnectLoopsInTypeHierarchy(Collection<JetType> supertypes) {
for (Iterator<JetType> iterator = supertypes.iterator(); iterator.hasNext(); ) { for (Iterator<JetType> iterator = supertypes.iterator(); iterator.hasNext(); ) {
JetType supertype = iterator.next(); JetType supertype = iterator.next();
if (isReachable(supertype.getConstructor(), this, new HashSet<TypeConstructor>())) { if (isReachable(supertype.getConstructor(), this, new HashSet<TypeConstructor>())) {
@@ -17,6 +17,7 @@
package org.jetbrains.jet.lang.resolve.lazy; package org.jetbrains.jet.lang.resolve.lazy;
import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Computable;
import com.intellij.util.Consumer;
import com.intellij.util.Function; import com.intellij.util.Function;
import com.intellij.util.containers.ConcurrentWeakValueHashMap; import com.intellij.util.containers.ConcurrentWeakValueHashMap;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -106,12 +107,37 @@ public class LockBasedStorageManager implements StorageManager {
return new LockBasedLazyValue<T>(lock, computable); return new LockBasedLazyValue<T>(lock, computable);
} }
@NotNull
@Override
public <T> LazyValue<T> createLazyValueWithPostCompute(@NotNull Computable<T> computable, @NotNull final Consumer<T> postCompute) {
return new LockBasedLazyValue<T>(lock, computable) {
@Override
protected void postCompute(@NotNull T value) {
postCompute.consume(value);
}
};
}
@NotNull @NotNull
@Override @Override
public <T> LazyValue<T> createNullableLazyValue(@NotNull Computable<T> computable) { public <T> LazyValue<T> createNullableLazyValue(@NotNull Computable<T> computable) {
return new LockBasedNullableLazyValue<T>(lock, computable); return new LockBasedNullableLazyValue<T>(lock, computable);
} }
@NotNull
@Override
public <T> LazyValue<T> createNullableLazyValueWithPostCompute(
@NotNull Computable<T> computable,
@NotNull final Consumer<T> postCompute
) {
return new LockBasedNullableLazyValue<T>(lock, computable) {
@Override
protected void postCompute(@Nullable T value) {
postCompute.consume(value);
}
};
}
@NotNull @NotNull
@Override @Override
public BindingTrace createSafeTrace(@NotNull BindingTrace originalTrace) { public BindingTrace createSafeTrace(@NotNull BindingTrace originalTrace) {
@@ -145,10 +171,15 @@ public class LockBasedStorageManager implements StorageManager {
if (_value == null) { if (_value == null) {
_value = computable.compute(); _value = computable.compute();
value = _value; value = _value;
postCompute(_value);
} }
return _value; return _value;
} }
} }
protected void postCompute(@NotNull T value) {
// Doing something in post-compute helps prevent infinite recursion
}
} }
private static class LockBasedNullableLazyValue<T> implements LazyValue<T> { private static class LockBasedNullableLazyValue<T> implements LazyValue<T> {
@@ -183,11 +214,17 @@ public class LockBasedStorageManager implements StorageManager {
value = _value; value = _value;
computed = true; computed = true;
postCompute(_value);
return _value; return _value;
} }
return value; return value;
} }
} }
protected void postCompute(@Nullable T value) {
// Doing something in post-compute helps prevent infinite recursion
}
} }
private static class LockProtectedTrace implements BindingTrace { private static class LockProtectedTrace implements BindingTrace {
@@ -17,6 +17,7 @@
package org.jetbrains.jet.lang.resolve.lazy; package org.jetbrains.jet.lang.resolve.lazy;
import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Computable;
import com.intellij.util.Consumer;
import com.intellij.util.Function; import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.resolve.BindingTrace; import org.jetbrains.jet.lang.resolve.BindingTrace;
@@ -35,9 +36,23 @@ public interface StorageManager {
@NotNull @NotNull
<T> LazyValue<T> createLazyValue(@NotNull Computable<T> computable); <T> LazyValue<T> createLazyValue(@NotNull Computable<T> computable);
/**
* {@code postCompute} is called after the value is computed, but before any other thread sees it (the current thread may
* see it in between)
*/
@NotNull
<T> LazyValue<T> createLazyValueWithPostCompute(@NotNull Computable<T> computable, @NotNull Consumer<T> postCompute);
@NotNull @NotNull
<T> LazyValue<T> createNullableLazyValue(@NotNull Computable<T> computable); <T> LazyValue<T> createNullableLazyValue(@NotNull Computable<T> computable);
/**
* {@code postCompute} is called after the value is computed, but before any other thread sees it (the current thread may
* see it in between)
*/
@NotNull
<T> LazyValue<T> createNullableLazyValueWithPostCompute(@NotNull Computable<T> computable, @NotNull Consumer<T> postCompute);
@NotNull @NotNull
BindingTrace createSafeTrace(@NotNull BindingTrace originalTrace); BindingTrace createSafeTrace(@NotNull BindingTrace originalTrace);
@@ -0,0 +1,3 @@
class A : <error>A</error>() {}
val x : Int = <error>A()</error>
@@ -243,6 +243,11 @@ public class JetPsiCheckerTestGenerated extends AbstractJetPsiCheckerTest {
doTest("idea/testData/checker/TraitSupertypeList.kt"); doTest("idea/testData/checker/TraitSupertypeList.kt");
} }
@TestMetadata("trivialHierarchyLoop.kt")
public void testTrivialHierarchyLoop() throws Exception {
doTest("idea/testData/checker/trivialHierarchyLoop.kt");
}
@TestMetadata("UnreachableCode.kt") @TestMetadata("UnreachableCode.kt")
public void testUnreachableCode() throws Exception { public void testUnreachableCode() throws Exception {
doTest("idea/testData/checker/UnreachableCode.kt"); doTest("idea/testData/checker/UnreachableCode.kt");