diff --git a/platform/core-api/src/com/intellij/psi/util/PsiTreeUtil.java b/platform/core-api/src/com/intellij/psi/util/PsiTreeUtil.java index 719460729007..06b104b76155 100644 --- a/platform/core-api/src/com/intellij/psi/util/PsiTreeUtil.java +++ b/platform/core-api/src/com/intellij/psi/util/PsiTreeUtil.java @@ -28,6 +28,7 @@ import com.intellij.psi.stubs.StubBase; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.templateLanguages.OuterLanguageElement; import com.intellij.util.ArrayUtil; +import com.intellij.util.PairProcessor; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -938,6 +939,24 @@ public class PsiTreeUtil { return true; } + public static boolean treeWalkUp(@NotNull final PsiElement entrance, + @Nullable final PsiElement maxScope, + PairProcessor eachScopeAndLastParent) { + PsiElement prevParent = null; + PsiElement scope = entrance; + + while (scope != null) { + if (!eachScopeAndLastParent.process(scope, prevParent)) return false; + + if (scope == maxScope) break; + prevParent = scope; + scope = prevParent.getContext(); + } + + return true; + + } + @NotNull public static PsiElement findPrevParent(@NotNull PsiElement ancestor, @NotNull PsiElement descendant) { PsiElement cur = descendant; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/DeclarationCacheKey.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/DeclarationCacheKey.java new file mode 100644 index 000000000000..41d88d1e975d --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/DeclarationCacheKey.java @@ -0,0 +1,200 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.lang.resolve; + +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiType; +import com.intellij.psi.ResolveState; +import com.intellij.psi.scope.PsiScopeProcessor; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.PairProcessor; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; +import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint; +import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.ConcurrentMap; + +/** +* @author peter +*/ +class DeclarationCacheKey { + private static final CachedValueProvider>> VALUE_PROVIDER = + new CachedValueProvider>>() { + @Nullable + @Override + public Result>> compute() { + ConcurrentMap> map = ContainerUtil.newConcurrentMap(); + return Result.create(map, PsiModificationTracker.MODIFICATION_COUNT); + } + }; + @Nullable private final String name; + @NotNull private final EnumSet kinds; + private final boolean nonCode; + + DeclarationCacheKey(@Nullable String name, ClassHint hint, boolean nonCode) { + this.name = name; + this.kinds = getResolveKinds(hint); + this.nonCode = nonCode; + } + + private static EnumSet getResolveKinds(ClassHint hint) { + EnumSet set = EnumSet.noneOf(ClassHint.ResolveKind.class); + for (ClassHint.ResolveKind kind : ClassHint.ResolveKind.values()) { + if (hint.shouldProcess(kind)) { + set.add(kind); + } + } + return set; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DeclarationCacheKey)) { + return false; + } + + DeclarationCacheKey key = (DeclarationCacheKey)o; + + if (nonCode != key.nonCode) { + return false; + } + if (!kinds.equals(key.kinds)) { + return false; + } + if (name != null ? !name.equals(key.name) : key.name != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + kinds.hashCode(); + result = 31 * result + (nonCode ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "DeclarationCacheKey{" + + "name='" + name + '\'' + + ", kinds=" + kinds + + ", nonCode=" + nonCode + + '}'; + } + + private List collectDeclarations(final GroovyPsiElement place) { + final ArrayList result = new ArrayList(); + PsiTreeUtil.treeWalkUp(place, null, new PairProcessor() { + @Override + public boolean process(PsiElement scope, PsiElement lastParent) { + result.add(collectScopeDeclarations(scope, lastParent, place)); + return true; + } + }); + return result; + } + + private DeclarationHolder collectScopeDeclarations(PsiElement scope, PsiElement lastParent, GroovyPsiElement place) { + MyCollectProcessor plainCollector = new MyCollectProcessor(scope); + MyCollectProcessor nonCodeCollector = new MyCollectProcessor(scope); + ResolveUtil.doProcessDeclarations(place, lastParent, scope, plainCollector, nonCode ? nonCodeCollector : null); + return new DeclarationHolder(scope, plainCollector.declarations, nonCodeCollector.declarations); + } + + private List getAllDeclarations(GroovyPsiElement place) { + ConcurrentMap> cache = + CachedValuesManager.getManager(place.getProject()).getCachedValue(place, VALUE_PROVIDER); + List declarations = cache.get(this); + if (declarations == null) { + cache.putIfAbsent(this, declarations = collectDeclarations(place)); + } + return declarations; + } + + boolean processCachedDeclarations(GroovyPsiElement place, PsiScopeProcessor processor) { + for (DeclarationHolder holder : getAllDeclarations(place)) { + if (!holder.processCachedDeclarations(processor)) { + return false; + } + } + return true; + } + + private static class DeclarationHolder { + final PsiElement scope; + final List> plainDeclarations; + final List> nonCodeDeclarations; + + private DeclarationHolder(PsiElement scope, + List> plainDeclarations, + List> nonCodeDeclarations) { + this.scope = scope; + this.plainDeclarations = plainDeclarations; + this.nonCodeDeclarations = nonCodeDeclarations; + } + + boolean processCachedDeclarations(PsiScopeProcessor processor) { + PsiScopeProcessor realProcessor = ResolveUtil.substituteProcessor(processor, scope); + for (Pair pair : plainDeclarations) { + if (!realProcessor.execute(pair.first, pair.second)) { + return false; + } + } + for (Pair pair : nonCodeDeclarations) { + if (!processor.execute(pair.first, pair.second)) { + return false; + } + } + + ResolveUtil.issueLevelChangeEvents(processor, scope); + return true; + } + + } + + private class MyCollectProcessor extends ResolverProcessor { + final List> declarations = CollectionFactory.arrayList(); + + public MyCollectProcessor(PsiElement scope) { + super(DeclarationCacheKey.this.name, DeclarationCacheKey.this.kinds, scope, PsiType.EMPTY_ARRAY); + } + + @Override + public boolean execute(@NotNull PsiElement element, ResolveState state) { + declarations.add(Pair.create(element, state)); + return true; + } + } +} + diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java index d418cc0d0c30..8f1cc512f2a1 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java @@ -25,6 +25,7 @@ import com.intellij.psi.scope.JavaScopeProcessorEvent; import com.intellij.psi.scope.NameHint; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.*; +import com.intellij.util.PairProcessor; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -86,33 +87,38 @@ public class ResolveUtil { * * @param place - place to start tree walk up * @param processor - * @param processNonCodeMethods - this parameter tells us if we need non code members. But non code members are started to process only after we walk up any code block or script + * @param processNonCodeMethods - this parameter tells us if we need non code members * @return */ - public static boolean treeWalkUp(@NotNull GroovyPsiElement place, PsiScopeProcessor processor, boolean processNonCodeMethods) { - PsiElement lastParent = null; - PsiElement run = place; - - final Project project = place.getProject(); - PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); - - while (run != null) { - if (!run.processDeclarations(substituteProcessor(processor, run), ResolveState.initial(), lastParent, place)) return false; - - if (processNonCodeMethods && !processScopeNonCodeMethods(place, processor, run, factory)) { - return false; - } - - issueLevelChangeEvents(processor, run); - - lastParent = run; - run = run.getContext(); + public static boolean treeWalkUp(@NotNull final GroovyPsiElement place, final PsiScopeProcessor processor, boolean processNonCodeMethods) { + ClassHint hint = processor.getHint(ClassHint.KEY); + if (hint != null) { + return new DeclarationCacheKey(getNameHint(processor), hint, processNonCodeMethods).processCachedDeclarations(place, processor); } + final PsiScopeProcessor nonCodeProcessor = processNonCodeMethods ? processor : null; + return PsiTreeUtil.treeWalkUp(place, null, new PairProcessor() { + @Override + public boolean process(PsiElement scope, PsiElement lastParent) { + if (!doProcessDeclarations(place, lastParent, scope, substituteProcessor(processor, scope), nonCodeProcessor)) { + return false; + } + issueLevelChangeEvents(processor, scope); + return true; + } + }); + } + + static boolean doProcessDeclarations(GroovyPsiElement place, + PsiElement lastParent, + PsiElement scope, + @NotNull PsiScopeProcessor plainProcessor, @Nullable PsiScopeProcessor nonCodeProcessor) { + if (!scope.processDeclarations(plainProcessor, ResolveState.initial(), lastParent, place)) return false; + if (nonCodeProcessor != null && !processScopeNonCodeMethods(place, nonCodeProcessor, scope)) return false; return true; } - private static void issueLevelChangeEvents(PsiScopeProcessor processor, PsiElement run) { + static void issueLevelChangeEvents(PsiScopeProcessor processor, PsiElement run) { processor.handleEvent(JavaScopeProcessorEvent.CHANGE_LEVEL, null); if (run instanceof GrClosableBlock && GrClosableBlock.OWNER_NAME.equals(getNameHint(processor))) { processor.handleEvent(DECLARATION_SCOPE_PASSED, run); @@ -122,7 +128,7 @@ public class ResolveUtil { } } - private static PsiScopeProcessor substituteProcessor(PsiScopeProcessor processor, PsiElement scope) { + static PsiScopeProcessor substituteProcessor(PsiScopeProcessor processor, PsiElement scope) { //hack for walking up in java code //java's processDeclarations don't check names so we should do it manually if (scope.getLanguage() != GroovyFileType.GROOVY_LANGUAGE && processor.getHint(NameHint.KEY) != null) { @@ -131,29 +137,32 @@ public class ResolveUtil { return processor; } - private static boolean processScopeNonCodeMethods(GroovyPsiElement place, - PsiScopeProcessor processor, - PsiElement scope, - PsiElementFactory factory) { + static boolean processScopeNonCodeMethods(GroovyPsiElement place, PsiScopeProcessor processor, PsiElement scope) { if (scope instanceof GrTypeDefinition) { - if (!processNonCodeMembers(factory.createType(((GrTypeDefinition)scope)), processor, place, ResolveState.initial())) return false; + return processNonCodeMembers(createPsiType((GrTypeDefinition)scope), processor, place, ResolveState.initial()); } - else if ((scope instanceof GroovyFileBase) && ((GroovyFileBase)scope).isScript()) { + + if (scope instanceof GroovyFileBase && ((GroovyFileBase)scope).isScript()) { final PsiClass psiClass = ((GroovyFileBase)scope).getScriptClass(); - if (psiClass != null) { - if (!processNonCodeMembers(factory.createType(psiClass), processor, place, ResolveState.initial())) return false; - } + return psiClass == null || processNonCodeMembers(createPsiType(psiClass), processor, place, ResolveState.initial()); } - else if (scope instanceof GrClosableBlock) { + + if (scope instanceof GrClosableBlock) { PsiClass superClass = getLiteralSuperClass((GrClosableBlock)scope); if (superClass != null && !superClass.processDeclarations(processor, ResolveState.initial(), null, place)) return false; if (!GdkMethodUtil.categoryIteration((GrClosableBlock)scope, processor, ResolveState.initial())) return false; if (!GdkMethodUtil.withIteration((GrClosableBlock)scope, processor)) return false; } + return true; } + private static PsiClassType createPsiType(PsiClass psiClass) { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(psiClass.getProject()); + return factory.createType(psiClass); + } + public static boolean processChildren(PsiElement element, PsiScopeProcessor processor, ResolveState state, diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy index dc93a7d3a83f..8ff6a9911833 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyStressPerformanceTest.groovy @@ -163,18 +163,46 @@ class Foo implements Bar { measureHighlighting(text, 200) } - public void "test using SSA variables in a for loop"() { + public void "test using non-reassigned for loop parameters"() { RecursionManager.assertOnRecursionPrevention(testRootDisposable) def text = """ -def foo(List list) { for (file in list) { ${ -" println bar(file)\n" * 10 +" println bar(file)\n" * 20 } } } def bar(File file) { file.path } """ - measureHighlighting(text, 300) + measureHighlighting(text, 2000) + } + + public void "test using SSA variables in a for loop"() { + //todo RecursionManager.assertOnRecursionPrevention(testRootDisposable) + def text = """ +def foo(List list, SomeClass sc) { + List result + for (s in list) { +${ +''' + bar(s, result) + bar2(s, result, sc) + bar3(foo:s, bar:result, sc) + sc.someMethod(s) +''' * 2 + } + } +} +def bar(String s, List result) { result << s } +def bar2(String s, List result) { result << s } +def bar2(int s, List result, SomeClass sc) { result << s as String } +def bar3(Map args, List result, SomeClass sc) { result << s as String } + +class SomeClass { + void someMethod(String s) {} +} +""" + measureHighlighting(text, 1500) } }