correctly don't cache when a method type inference is canceled because it went too deep

This commit is contained in:
peter
2011-03-23 17:26:14 +01:00
parent f3f444a188
commit e811167912
8 changed files with 78 additions and 57 deletions
@@ -17,6 +17,8 @@ package com.intellij.openapi.util;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author peter
*/
@@ -26,6 +28,10 @@ public interface RecursionGuard {
StackStamp markStack();
List<Object> currentStack();
void prohibitResultCaching(Object since);
interface StackStamp {
boolean mayCacheNow();
}
@@ -15,7 +15,9 @@
*/
package com.intellij.openapi.util;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
@@ -29,10 +31,10 @@ public class RecursionManager {
return 0;
}
};
private static final ThreadLocal<LinkedHashMap<Object, Integer>> ourProgress = new ThreadLocal<LinkedHashMap<Object, Integer>>() {
private static final ThreadLocal<LinkedHashMap<Pair<String, Object>, Integer>> ourProgress = new ThreadLocal<LinkedHashMap<Pair<String, Object>, Integer>>() {
@Override
protected LinkedHashMap<Object, Integer> initialValue() {
return new LinkedHashMap<Object, Integer>();
protected LinkedHashMap<Pair<String, Object>, Integer> initialValue() {
return new LinkedHashMap<Pair<String, Object>, Integer>();
}
};
@@ -40,21 +42,10 @@ public class RecursionManager {
return new RecursionGuard() {
@Override
public <T> T doPreventingRecursion(Object key, Computable<T> computation) {
Object realKey = Pair.create(id, key);
LinkedHashMap<Object, Integer> progressMap = ourProgress.get();
Pair<String, Object> realKey = Pair.create(id, key);
LinkedHashMap<Pair<String, Object>, Integer> progressMap = ourProgress.get();
if (progressMap.containsKey(realKey)) {
int stamp = ourStamp.get() + 1;
ourStamp.set(stamp);
boolean inLoop = false;
for (Map.Entry<Object, Integer> entry: progressMap.entrySet()) {
if (inLoop) {
entry.setValue(stamp);
}
else if (entry.getKey().equals(realKey)) {
inLoop = true;
}
}
prohibitResultCaching(key);
return null;
}
@@ -79,6 +70,34 @@ public class RecursionManager {
}
};
}
@Override
public List<Object> currentStack() {
ArrayList<Object> result = new ArrayList<Object>();
LinkedHashMap<Pair<String, Object>, Integer> map = ourProgress.get();
for (Pair<String, Object> pair : map.keySet()) {
if (pair.first == id) {
result.add(pair.second);
}
}
return result;
}
@Override
public void prohibitResultCaching(Object since) {
int stamp = ourStamp.get() + 1;
ourStamp.set(stamp);
boolean inLoop = false;
for (Map.Entry<Pair<String, Object>, Integer> entry: ourProgress.get().entrySet()) {
if (inLoop) {
entry.setValue(stamp);
}
else if (entry.getKey().first.equals(id) && entry.getKey().second.equals(since)) {
inLoop = true;
}
}
}
};
}
@@ -46,7 +46,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefini
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.stubs.GroovyShortNamesCache;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -74,6 +73,7 @@ public class GroovyPsiManager {
private final TypeInferenceHelper myTypeInferenceHelper;
private static final String SYNTHETIC_CLASS_TEXT = "class __ARRAY__ { public int length }";
private static final RecursionGuard ourGuard = RecursionManager.createGuard("groovyPsiManager");
public GroovyPsiManager(Project project) {
myProject = project;
@@ -160,7 +160,7 @@ public class GroovyPsiManager {
public <T extends GroovyPsiElement> PsiType getType(T element, Function<T, PsiType> calculator) {
PsiType type = myCalculatedTypes.get(element);
if (type == null) {
RecursionGuard.StackStamp stamp = RecursionManager.createGuard("groovyPsiManager").markStack();
RecursionGuard.StackStamp stamp = ourGuard.markStack();
type = calculator.fun(element);
if (type == null) {
type = PsiType.NULL;
@@ -193,30 +193,15 @@ public class GroovyPsiManager {
return myArrayClass;
}
private static final ThreadLocal<List<PsiElement>> myElementsWithTypesBeingInferred = new ThreadLocal<List<PsiElement>>() {
protected List<PsiElement> initialValue() {
return new ArrayList<PsiElement>();
}
};
@Nullable
public static PsiType inferType(PsiElement element, Computable<PsiType> computable) {
final List<PsiElement> curr = myElementsWithTypesBeingInferred.get();
if (curr.size() > 7) { //don't end up walking the whole project PSI
List<Object> stack = ourGuard.currentStack();
if (stack.size() > 7) { //don't end up walking the whole project PSI
ourGuard.prohibitResultCaching(stack.get(0));
return null;
}
try {
curr.add(element);
return computable.compute();
}
finally {
curr.remove(element);
}
}
public static boolean isTypeBeingInferred(PsiElement element) {
return myElementsWithTypesBeingInferred.get().contains(element);
return ourGuard.doPreventingRecursion(element, computable);
}
public GroovyShortNamesCache getNamesCache() {
@@ -218,9 +218,6 @@ public class GrClosableBlockImpl extends GrBlockImpl implements GrClosableBlock
@Nullable
public PsiType getReturnType() {
if (GroovyPsiManager.isTypeBeingInferred(this)) {
return null;
}
return GroovyPsiManager.getInstance(getProject()).getType(this, ourTypesCalculator);
}
}
@@ -426,7 +426,7 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl<GrExpressi
} else if (resolved instanceof PsiVariable) {
result = ((PsiVariable) resolved).getType();
} else
if (resolved instanceof PsiMethod && !GroovyPsiManager.isTypeBeingInferred(resolved)) {
if (resolved instanceof PsiMethod) {
if (dotType == GroovyTokenTypes.mMEMBER_POINTER) {
return TypesUtil.createTypeByFQClassName(GroovyCommonClassNames.GROOVY_LANG_CLOSURE, this);
}
@@ -27,7 +27,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpres
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
@@ -49,7 +48,7 @@ public class DefaultCallExpressionTypeCalculator extends GrCallExpressionTypeCal
for (GroovyResolveResult resolveResult : resolveResults) {
PsiElement resolved = resolveResult.getElement();
PsiType returnType = null;
if (resolved instanceof PsiMethod && !GroovyPsiManager.isTypeBeingInferred(resolved)) {
if (resolved instanceof PsiMethod) {
PsiMethod method = (PsiMethod) resolved;
if (resolveResult.isInvokedOnProperty()) {
final PsiType propertyType = PsiUtil.getSmartReturnType(method);
@@ -211,10 +211,6 @@ public abstract class GrMethodBaseImpl extends GrStubElementBase<GrMethodStub> i
if (block == null) return null;
assert block.isValid() : "invalid code block";
if (GroovyPsiManager.isTypeBeingInferred(method)) {
return null;
}
return GroovyPsiManager.inferType(method, new MethodTypeInferencer(block));
}
};
@@ -1,15 +1,17 @@
package org.jetbrains.plugins.groovy.lang
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.testFramework.IdeaTestUtil
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.annotations.NotNull
import org.jetbrains.plugins.groovy.LightGroovyTestCase
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
import org.jetbrains.annotations.NotNull
import com.intellij.psi.PsiDocumentManager
import com.intellij.testFramework.IdeaTestUtil
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager
/**
/**
* @author peter
*/
class GroovyStressTest extends LightCodeInsightFixtureTestCase {
@@ -19,24 +21,41 @@ class GroovyStressTest extends LightCodeInsightFixtureTestCase {
}
public void testDontWalkLongInferenceChain() throws Exception {
Map<Integer, PsiClass> classes = [:]
myFixture.addFileToProject "Foo0.groovy", """class Foo0 {
def foo() { return 0 }
}"""
def max = 100
for (i in 1..max) {
myFixture.addFileToProject "Foo${i}.groovy", """class Foo$i {
def foo() { return Foo${i-1}.foo() }
}"""
def file = myFixture.addFileToProject("Foo${i}.groovy", """class Foo$i {
def foo() { return Foo${i - 1}.foo() }
}""")
classes[i] = (file as GroovyFile).classes[0]
}
def deepFile = myFixture.addFileToProject("DeepTest.groovy", "def test() { return Foo${max}.foo() }") as GroovyFile
assert Object.name == (deepFile.scriptClass.findMethodsByName("test", false)[0] as GrMethod).inferredReturnType.canonicalText
assert Object.name == inferredType(deepFile.scriptClass, 'test')
def shallowFile = myFixture.addFileToProject("ShallowTest.groovy", "def test() { return Foo2.foo() }") as GroovyFile
assert Integer.name == (shallowFile.scriptClass.findMethodsByName("test", false)[0] as GrMethod).inferredReturnType.canonicalText
assert Integer.name == inferredType(shallowFile.scriptClass, 'test')
int border = (1..max).find { int i ->
GroovyPsiManager.getInstance(project).dropTypesCache()
return inferredType(classes[i], 'foo') == Object.name
}
assert border
GroovyPsiManager.getInstance(project).dropTypesCache()
assert inferredType(classes[border], 'foo') == Object.name
assert inferredType(classes[border - 1], 'foo') == Integer.name
}
String inferredType(PsiClass clazz, String method) {
(clazz.findMethodsByName(method, false)[0] as GrMethod).inferredReturnType.canonicalText
}
public void testQuickIncrementalReparse() {
def story = '''scenario {
given "some precondition", {