mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
correctly don't cache when a method type inference is canceled because it went too deep
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+6
-21
@@ -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() {
|
||||
|
||||
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
+1
-2
@@ -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);
|
||||
|
||||
-4
@@ -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", {
|
||||
|
||||
Reference in New Issue
Block a user