cache declarations when walking up

This commit is contained in:
peter
2012-08-31 16:38:05 +02:00
parent 0c1aee28d0
commit 89ad0dbcbc
4 changed files with 291 additions and 35 deletions
@@ -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<PsiElement, PsiElement> 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;
@@ -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<ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>>> VALUE_PROVIDER =
new CachedValueProvider<ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>>>() {
@Nullable
@Override
public Result<ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>>> compute() {
ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>> map = ContainerUtil.newConcurrentMap();
return Result.create(map, PsiModificationTracker.MODIFICATION_COUNT);
}
};
@Nullable private final String name;
@NotNull private final EnumSet<ClassHint.ResolveKind> 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<ClassHint.ResolveKind> getResolveKinds(ClassHint hint) {
EnumSet<ClassHint.ResolveKind> 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<DeclarationHolder> collectDeclarations(final GroovyPsiElement place) {
final ArrayList<DeclarationHolder> result = new ArrayList<DeclarationHolder>();
PsiTreeUtil.treeWalkUp(place, null, new PairProcessor<PsiElement, PsiElement>() {
@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<DeclarationHolder> getAllDeclarations(GroovyPsiElement place) {
ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>> cache =
CachedValuesManager.getManager(place.getProject()).getCachedValue(place, VALUE_PROVIDER);
List<DeclarationHolder> 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<Pair<PsiElement, ResolveState>> plainDeclarations;
final List<Pair<PsiElement, ResolveState>> nonCodeDeclarations;
private DeclarationHolder(PsiElement scope,
List<Pair<PsiElement, ResolveState>> plainDeclarations,
List<Pair<PsiElement, ResolveState>> nonCodeDeclarations) {
this.scope = scope;
this.plainDeclarations = plainDeclarations;
this.nonCodeDeclarations = nonCodeDeclarations;
}
boolean processCachedDeclarations(PsiScopeProcessor processor) {
PsiScopeProcessor realProcessor = ResolveUtil.substituteProcessor(processor, scope);
for (Pair<PsiElement, ResolveState> pair : plainDeclarations) {
if (!realProcessor.execute(pair.first, pair.second)) {
return false;
}
}
for (Pair<PsiElement, ResolveState> pair : nonCodeDeclarations) {
if (!processor.execute(pair.first, pair.second)) {
return false;
}
}
ResolveUtil.issueLevelChangeEvents(processor, scope);
return true;
}
}
private class MyCollectProcessor extends ResolverProcessor {
final List<Pair<PsiElement, ResolveState>> 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;
}
}
}
@@ -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<PsiElement, PsiElement>() {
@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,
@@ -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<File list) {
def foo(List<File> 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<String> list, SomeClass sc) {
List<String> 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<String> result) { result << s }
def bar2(String s, List<String> result) { result << s }
def bar2(int s, List<String> result, SomeClass sc) { result << s as String }
def bar3(Map args, List<String> result, SomeClass sc) { result << s as String }
class SomeClass {
void someMethod(String s) {}
}
"""
measureHighlighting(text, 1500)
}
}