diff --git a/platform/platform-api/src/com/intellij/openapi/util/RecursionGuard.java b/platform/platform-api/src/com/intellij/openapi/util/RecursionGuard.java index 4e276b6dba9f..bcfa3415ced4 100644 --- a/platform/platform-api/src/com/intellij/openapi/util/RecursionGuard.java +++ b/platform/platform-api/src/com/intellij/openapi/util/RecursionGuard.java @@ -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 currentStack(); + + void prohibitResultCaching(Object since); + interface StackStamp { boolean mayCacheNow(); } diff --git a/platform/platform-api/src/com/intellij/openapi/util/RecursionManager.java b/platform/platform-api/src/com/intellij/openapi/util/RecursionManager.java index ceb38c14b88f..ce4d7ce92d24 100644 --- a/platform/platform-api/src/com/intellij/openapi/util/RecursionManager.java +++ b/platform/platform-api/src/com/intellij/openapi/util/RecursionManager.java @@ -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> ourProgress = new ThreadLocal>() { + private static final ThreadLocal, Integer>> ourProgress = new ThreadLocal, Integer>>() { @Override - protected LinkedHashMap initialValue() { - return new LinkedHashMap(); + protected LinkedHashMap, Integer> initialValue() { + return new LinkedHashMap, Integer>(); } }; @@ -40,21 +42,10 @@ public class RecursionManager { return new RecursionGuard() { @Override public T doPreventingRecursion(Object key, Computable computation) { - Object realKey = Pair.create(id, key); - LinkedHashMap progressMap = ourProgress.get(); + Pair realKey = Pair.create(id, key); + LinkedHashMap, Integer> progressMap = ourProgress.get(); if (progressMap.containsKey(realKey)) { - int stamp = ourStamp.get() + 1; - ourStamp.set(stamp); - - boolean inLoop = false; - for (Map.Entry 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 currentStack() { + ArrayList result = new ArrayList(); + LinkedHashMap, Integer> map = ourProgress.get(); + for (Pair 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, Integer> entry: ourProgress.get().entrySet()) { + if (inLoop) { + entry.setValue(stamp); + } + else if (entry.getKey().first.equals(id) && entry.getKey().second.equals(since)) { + inLoop = true; + } + } + } }; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiManager.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiManager.java index a6ae1a4dbbae..5a115f91f232 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiManager.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyPsiManager.java @@ -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 PsiType getType(T element, Function 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> myElementsWithTypesBeingInferred = new ThreadLocal>() { - protected List initialValue() { - return new ArrayList(); - } - }; - @Nullable public static PsiType inferType(PsiElement element, Computable computable) { - final List curr = myElementsWithTypesBeingInferred.get(); - if (curr.size() > 7) { //don't end up walking the whole project PSI + List 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() { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrClosableBlockImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrClosableBlockImpl.java index e6d1488e6c06..0da8e56ad589 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrClosableBlockImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrClosableBlockImpl.java @@ -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); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java index cd57d81ee9be..8d59132d8a46 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java @@ -426,7 +426,7 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl 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)); } }; diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressTest.groovy index 82688c38d129..763ad3db5494 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressTest.groovy @@ -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 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", {