speedup functional expression search (IDEA-159107)

* filter inapplicable expressions without loading AST if possible, via approximate resolve
* for that, store the approximate call chains in index
* iterate over files once, not for each empty marker Runnable interface separately
* don't rely on generic method parameter index: it's huge, memory-hungry and works only in Java
This commit is contained in:
peter
2016-08-13 12:00:02 +02:00
parent e8ea2598f3
commit 9776d5a28d
12 changed files with 762 additions and 678 deletions

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2000-2016 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 com.intellij.psi.impl.java;
import com.google.common.base.MoreObjects;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.search.ApproximateResolver;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.util.io.IOUtil;
import org.jetbrains.annotations.NotNull;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author peter
*/
public class FunExprOccurrence {
public final int funExprOffset;
private final int argIndex;
private final List<ReferenceChainLink> referenceContext;
public FunExprOccurrence(int funExprOffset,
int argIndex,
List<ReferenceChainLink> referenceContext) {
this.funExprOffset = funExprOffset;
this.argIndex = argIndex;
this.referenceContext = referenceContext;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FunExprOccurrence)) return false;
FunExprOccurrence that = (FunExprOccurrence)o;
if (funExprOffset != that.funExprOffset) return false;
if (argIndex != that.argIndex) return false;
if (!referenceContext.equals(that.referenceContext)) return false;
return true;
}
@Override
public int hashCode() {
int result = funExprOffset;
result = 31 * result + argIndex;
result = 31 * result + referenceContext.hashCode();
return result;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("offset", funExprOffset)
.add("argIndex", argIndex)
.add("chain", referenceContext)
.toString();
}
void serialize(DataOutput out) throws IOException {
DataInputOutputUtil.writeINT(out, funExprOffset);
DataInputOutputUtil.writeINT(out, argIndex);
DataInputOutputUtil.writeINT(out, referenceContext.size());
for (ReferenceChainLink link : referenceContext) {
serializeLink(out, link);
}
}
static FunExprOccurrence deserialize(DataInput in) throws IOException {
int offset = DataInputOutputUtil.readINT(in);
int argIndex = DataInputOutputUtil.readINT(in);
int contextSize = DataInputOutputUtil.readINT(in);
List<ReferenceChainLink> context = new ArrayList<>(contextSize);
for (int i = 0; i < contextSize; i++) {
context.add(deserializeLink(in));
}
return new FunExprOccurrence(offset, argIndex, context);
}
private static void serializeLink(DataOutput out, ReferenceChainLink link) throws IOException {
IOUtil.writeUTF(out, link.referenceName);
out.writeBoolean(link.isCall);
if (link.isCall) {
DataInputOutputUtil.writeINT(out, link.argCount);
}
}
@NotNull
private static ReferenceChainLink deserializeLink(DataInput in) throws IOException {
String referenceName = IOUtil.readUTF(in);
boolean isCall = in.readBoolean();
return new ReferenceChainLink(referenceName, isCall, isCall ? DataInputOutputUtil.readINT(in) : -1);
}
public boolean canHaveType(@NotNull List<PsiClass> samClasses, @NotNull VirtualFile placeFile) {
if (referenceContext.isEmpty()) return true;
Set<PsiClass> qualifiers = null;
for (int i = 0; i < referenceContext.size(); i++) {
ReferenceChainLink link = referenceContext.get(i);
List<? extends PsiMember> candidates = i == 0 ? link.getGlobalMembers(placeFile, samClasses.get(0).getProject())
: link.getSymbolMembers(qualifiers);
if (candidates == null) return true;
if (i == referenceContext.size() - 1) {
return ContainerUtil.exists(candidates, m -> isCompatible(link, m, samClasses));
}
qualifiers = ApproximateResolver.getDefiniteSymbolTypes(candidates);
if (qualifiers == null) return true;
}
return true;
}
private boolean isCompatible(ReferenceChainLink link, PsiMember member, List<PsiClass> samClasses) {
if (link.isCall) {
return member instanceof PsiMethod && hasCompatibleParameter((PsiMethod)member, argIndex, samClasses);
}
if (member instanceof PsiClass) {
return ContainerUtil.exists(samClasses, c -> InheritanceUtil.isInheritorOrSelf((PsiClass)member, c, true));
}
return member instanceof PsiField &&
ContainerUtil.exists(samClasses, c -> canPassFunctionalExpression(c, ((PsiField)member).getType()));
}
public static boolean hasCompatibleParameter(PsiMethod method, int argIndex, List<PsiClass> samClasses) {
PsiParameter[] parameters = method.getParameterList().getParameters();
int paramIndex = method.isVarArgs() ? Math.min(argIndex, parameters.length - 1) : argIndex;
return paramIndex < parameters.length &&
ContainerUtil.exists(samClasses, c -> canPassFunctionalExpression(c, parameters[paramIndex].getType()));
}
private static boolean canPassFunctionalExpression(PsiClass sam, PsiType paramType) {
if (paramType instanceof PsiEllipsisType) {
paramType = ((PsiEllipsisType)paramType).getComponentType();
}
PsiClass functionalCandidate = PsiUtil.resolveClassInClassTypeOnly(paramType);
if (functionalCandidate instanceof PsiTypeParameter) {
return InheritanceUtil.isInheritorOrSelf(sam, PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(paramType)), true);
}
return InheritanceUtil.isInheritorOrSelf(functionalCandidate, sam, true);
}
}

View File

@@ -20,11 +20,13 @@ import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiBinaryExpression;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.FileLocalResolver;
import com.intellij.psi.impl.source.JavaFileElementType;
import com.intellij.psi.impl.source.JavaLightTreeUtil;
import com.intellij.psi.impl.source.tree.ElementType;
@@ -32,26 +34,26 @@ import com.intellij.psi.impl.source.tree.LightTreeUtil;
import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.util.io.KeyDescriptor;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<FunctionalExpressionKey, TIntArrayList> implements PsiDependentIndex {
public static final ID<FunctionalExpressionKey, TIntArrayList> INDEX_ID = ID.create("java.fun.expression");
public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<FunctionalExpressionKey, List<FunExprOccurrence>> implements PsiDependentIndex {
public static final ID<FunctionalExpressionKey, List<FunExprOccurrence>> INDEX_ID = ID.create("java.fun.expression");
private static final KeyDescriptor<FunctionalExpressionKey> KEY_DESCRIPTOR = new KeyDescriptor<FunctionalExpressionKey>() {
@Override
public int getHashCode(FunctionalExpressionKey value) {
@@ -75,33 +77,100 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
};
@NotNull
private static FunctionalExpressionKey.Location calcLocation(LighterAST tree, LighterASTNode funExpr) {
LighterASTNode call = getContainingCall(tree, funExpr);
List<LighterASTNode> args = JavaLightTreeUtil.getArgList(tree, call);
int argIndex = args == null ? -1 : getArgIndex(args, funExpr);
String methodName = call == null ? null : getCalledMethodName(tree, call);
return methodName == null || argIndex < 0
? createTypedLocation(tree, funExpr)
: new FunctionalExpressionKey.CallLocation(methodName, args.size(), argIndex, StreamApiDetector.isStreamApiCall(tree, call));
private static List<ReferenceChainLink> createCallChain(FileLocalResolver resolver, @Nullable LighterASTNode expr) {
List<ReferenceChainLink> chain = new ArrayList<>();
while (true) {
if (expr == null) return reversedChain(chain);
if (expr.getTokenType() == PARENTH_EXPRESSION) {
expr = LightTreeUtil.firstChildOfType(resolver.getLightTree(), expr, ElementType.EXPRESSION_BIT_SET);
continue;
}
if (expr.getTokenType() == TYPE_CAST_EXPRESSION) {
String typeName = resolver.getShortClassTypeName(expr);
ContainerUtil.addIfNotNull(chain, typeName != null ? new ReferenceChainLink(typeName, false, -1) : null);
return reversedChain(chain);
}
boolean isCall = expr.getTokenType() == METHOD_CALL_EXPRESSION || expr.getTokenType() == NEW_EXPRESSION;
String referenceName = getReferencedMemberName(resolver.getLightTree(), expr, isCall);
if (referenceName == null) return reversedChain(chain);
LighterASTNode qualifier = getQualifier(resolver.getLightTree(), expr, isCall);
if (qualifier == null) {
ContainerUtil.addIfNotNull(chain, createChainStart(resolver, expr, isCall, referenceName));
return reversedChain(chain);
}
chain.add(new ReferenceChainLink(referenceName, isCall, getArgCount(resolver.getLightTree(), expr)));
expr = qualifier;
}
}
@NotNull
private static FunctionalExpressionKey.Location createTypedLocation(LighterAST tree, LighterASTNode funExpr) {
LighterASTNode scope = skipExpressionsUp(tree, funExpr, TokenSet.create(LOCAL_VARIABLE, FIELD, TYPE_CAST_EXPRESSION, RETURN_STATEMENT));
private static List<ReferenceChainLink> reversedChain(List<ReferenceChainLink> chain) {
Collections.reverse(chain);
return chain;
}
private static int getArgCount(LighterAST tree, LighterASTNode expr) {
List<LighterASTNode> args = JavaLightTreeUtil.getArgList(tree, expr);
return args == null ? -1 : args.size();
}
@Nullable
private static LighterASTNode getQualifier(LighterAST tree, LighterASTNode expr, boolean isCall) {
LighterASTNode qualifier = tree.getChildren(expr).get(0);
if (isCall) {
List<LighterASTNode> children = tree.getChildren(qualifier);
qualifier = children.isEmpty() ? null : children.get(0);
}
return qualifier != null && ElementType.EXPRESSION_BIT_SET.contains(qualifier.getTokenType()) ? qualifier : null;
}
@Nullable
private static String getReferencedMemberName(LighterAST tree, LighterASTNode expr, boolean isCall) {
if (isCall) {
return getCalledMethodName(tree, expr);
}
if (expr.getTokenType() == REFERENCE_EXPRESSION) {
return JavaLightTreeUtil.getNameIdentifierText(tree, expr);
}
return null;
}
@Nullable
private static ReferenceChainLink createChainStart(FileLocalResolver resolver,
LighterASTNode expr,
boolean isCall,
String referenceName) {
if (!isCall) {
FileLocalResolver.LightResolveResult result = resolver.resolveLocally(expr);
if (result == FileLocalResolver.LightResolveResult.UNKNOWN) return null;
LighterASTNode target = result.getTarget();
if (target != null) {
String typeName = resolver.getShortClassTypeName(target);
return typeName != null ? new ReferenceChainLink(typeName, false, -1) : null;
}
}
return new ReferenceChainLink(referenceName, isCall, getArgCount(resolver.getLightTree(), expr));
}
@NotNull
private static String calcExprType(LighterASTNode funExpr, FileLocalResolver resolver) {
LighterASTNode scope = skipExpressionsUp(resolver.getLightTree(), funExpr, TokenSet.create(LOCAL_VARIABLE, FIELD, TYPE_CAST_EXPRESSION, RETURN_STATEMENT, ASSIGNMENT_EXPRESSION));
if (scope != null) {
if (scope.getTokenType() == RETURN_STATEMENT) {
scope = LightTreeUtil.getParentOfType(tree, scope,
if (scope.getTokenType() == ASSIGNMENT_EXPRESSION) {
LighterASTNode lValue = findExpressionChild(scope, resolver.getLightTree());
scope = lValue == null ? null : resolver.resolveLocally(lValue).getTarget();
}
else if (scope.getTokenType() == RETURN_STATEMENT) {
scope = LightTreeUtil.getParentOfType(resolver.getLightTree(), scope,
TokenSet.create(METHOD),
TokenSet.orSet(ElementType.MEMBER_BIT_SET, TokenSet.create(LAMBDA_EXPRESSION)));
}
LighterASTNode typeElement = LightTreeUtil.firstChildOfType(tree, scope, TYPE);
String typeText = JavaLightTreeUtil.getNameIdentifierText(tree, LightTreeUtil.firstChildOfType(tree, typeElement, JAVA_CODE_REFERENCE));
if (typeText != null) {
return new FunctionalExpressionKey.TypedLocation(typeText);
}
}
return FunctionalExpressionKey.Location.UNKNOWN;
return StringUtil.notNullize(scope == null ? null : resolver.getShortClassTypeName(scope));
}
private static int getArgIndex(List<LighterASTNode> args, LighterASTNode expr) {
@@ -113,7 +182,7 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
return -1;
}
private static FunctionalExpressionKey.CoarseType calcType(final LighterAST tree, LighterASTNode funExpr) {
private static FunctionalExpressionKey.CoarseType calcReturnType(final LighterAST tree, LighterASTNode funExpr) {
if (funExpr.getTokenType() == METHOD_REF_EXPRESSION) return FunctionalExpressionKey.CoarseType.UNKNOWN;
LighterASTNode block = LightTreeUtil.firstChildOfType(tree, funExpr, CODE_BLOCK);
@@ -161,6 +230,7 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
return returnsSomething.get() ? FunctionalExpressionKey.CoarseType.NON_VOID : FunctionalExpressionKey.CoarseType.VOID;
}
@Nullable
private static LighterASTNode findExpressionChild(@NotNull LighterASTNode element, LighterAST tree) {
return LightTreeUtil.firstChildOfType(tree, element, ElementType.EXPRESSION_BIT_SET);
}
@@ -221,8 +291,12 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
@Nullable
private static String getSuperClassName(LighterAST tree, LighterASTNode call) {
LighterASTNode aClass = findClass(tree, call);
LighterASTNode extendsList = LightTreeUtil.firstChildOfType(tree, aClass, EXTENDS_LIST);
return JavaLightTreeUtil.getNameIdentifierText(tree, LightTreeUtil.firstChildOfType(tree, extendsList, JAVA_CODE_REFERENCE));
return getReferenceName(tree, LightTreeUtil.firstChildOfType(tree, aClass, EXTENDS_LIST));
}
@Nullable
private static String getReferenceName(LighterAST tree, LighterASTNode refParent) {
return JavaLightTreeUtil.getNameIdentifierText(tree, LightTreeUtil.firstChildOfType(tree, refParent, JAVA_CODE_REFERENCE));
}
@Nullable
@@ -252,12 +326,7 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
}
private static LighterASTNode findClass(LighterAST tree, LighterASTNode node) {
while (node != null) {
final IElementType type = node.getTokenType();
if (type == CLASS) return node;
node = tree.getParent(node);
}
return null;
return JBIterable.generate(node, tree::getParent).find(n -> n.getTokenType() == CLASS);
}
@NotNull
@@ -268,35 +337,38 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
@Override
public int getVersion() {
return 1;
return 2;
}
@NotNull
@Override
public ID<FunctionalExpressionKey, TIntArrayList> getName() {
public ID<FunctionalExpressionKey, List<FunExprOccurrence>> getName() {
return INDEX_ID;
}
@NotNull
@Override
public DataIndexer<FunctionalExpressionKey, TIntArrayList, FileContent> getIndexer() {
public DataIndexer<FunctionalExpressionKey, List<FunExprOccurrence>, FileContent> getIndexer() {
return inputData -> {
Map<FunctionalExpressionKey, TIntArrayList> result = new HashMap<>();
CharSequence text = inputData.getContentAsText();
if (!StringUtil.contains(text, "->") && !StringUtil.contains(text, "::")) return Collections.emptyMap();
Map<FunctionalExpressionKey, List<FunExprOccurrence>> result = new HashMap<>();
LighterAST tree = ((FileContentImpl)inputData).getLighterASTForPsiDependentIndex();
FileLocalResolver resolver = new FileLocalResolver(tree);
new RecursiveLighterASTNodeWalkingVisitor(tree) {
@Override
public void visitNode(@NotNull LighterASTNode element) {
if (element.getTokenType() == METHOD_REF_EXPRESSION ||
element.getTokenType() == LAMBDA_EXPRESSION) {
FunctionalExpressionKey key = new FunctionalExpressionKey(getFunExprParameterCount(tree, element),
calcType(tree, element),
calcLocation(tree, element));
TIntArrayList list = result.get(key);
calcReturnType(tree, element),
calcExprType(element, resolver));
List<FunExprOccurrence> list = result.get(key);
if (list == null) {
result.put(key, list = new TIntArrayList());
result.put(key, list = new SmartList<>());
}
list.add(element.getStartOffset());
list.add(createOccurrence(element, resolver));
}
super.visitNode(element);
@@ -307,24 +379,43 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
};
}
@NotNull
private static FunExprOccurrence createOccurrence(@NotNull LighterASTNode funExpr, FileLocalResolver resolver) {
LighterAST tree = resolver.getLightTree();
LighterASTNode containingCall = getContainingCall(tree, funExpr);
List<LighterASTNode> args = JavaLightTreeUtil.getArgList(tree, containingCall);
int argIndex = args == null ? -1 : getArgIndex(args, funExpr);
LighterASTNode chainExpr = containingCall;
if (chainExpr == null) {
LighterASTNode assignment = skipExpressionsUp(tree, funExpr, TokenSet.create(ASSIGNMENT_EXPRESSION));
if (assignment != null) {
chainExpr = findExpressionChild(assignment, tree);
}
}
return new FunExprOccurrence(funExpr.getStartOffset(), argIndex,
createCallChain(resolver, chainExpr));
}
@NotNull
@Override
public DataExternalizer<TIntArrayList> getValueExternalizer() {
return new DataExternalizer<TIntArrayList>() {
public DataExternalizer<List<FunExprOccurrence>> getValueExternalizer() {
return new DataExternalizer<List<FunExprOccurrence>>() {
@Override
public void save(@NotNull DataOutput out, TIntArrayList value) throws IOException {
public void save(@NotNull DataOutput out, List<FunExprOccurrence> value) throws IOException {
DataInputOutputUtil.writeINT(out, value.size());
for (int i = 0; i < value.size(); i++) {
DataInputOutputUtil.writeINT(out, value.get(i));
for (FunExprOccurrence info : value) {
info.serialize(out);
}
}
@Override
public TIntArrayList read(@NotNull DataInput in) throws IOException {
public List<FunExprOccurrence> read(@NotNull DataInput in) throws IOException {
int length = DataInputOutputUtil.readINT(in);
TIntArrayList list = new TIntArrayList(length);
List<FunExprOccurrence> list = new SmartList<FunExprOccurrence>();
for (int i = 0; i < length; i++) {
list.add(DataInputOutputUtil.readINT(in));
list.add(FunExprOccurrence.deserialize(in));
}
return list;
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2000-2016 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 com.intellij.psi.impl.java;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.ResolveScopeManager;
import com.intellij.psi.impl.search.ApproximateResolver;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author peter
*/
public class ReferenceChainLink {
final String referenceName;
final boolean isCall;
final int argCount;
public ReferenceChainLink(@NotNull String referenceName, boolean isCall, int argCount) {
this.referenceName = referenceName;
this.isCall = isCall;
this.argCount = argCount;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ReferenceChainLink)) return false;
ReferenceChainLink link = (ReferenceChainLink)o;
if (isCall != link.isCall) return false;
if (argCount != link.argCount) return false;
if (!referenceName.equals(link.referenceName)) return false;
return true;
}
@Override
public int hashCode() {
int result = referenceName.hashCode();
result = 31 * result + (isCall ? 1 : 0);
result = 31 * result + argCount;
return result;
}
@Override
public String toString() {
return referenceName + (isCall ? "(" + argCount + ")" : "");
}
private static final Key<Set<ReferenceChainLink>> EXPENSIVE_LINKS = Key.create("EXPENSIVE_CHAIN_LINKS");
@Nullable
List<PsiMember> getGlobalMembers(VirtualFile placeFile, Project project) {
if (isExpensive(project)) return null;
List<PsiMember> candidates = new ArrayList<>();
AtomicInteger count = new AtomicInteger();
Processor<PsiMember> processor = member -> {
if (canBeAccessible(placeFile, member) && (!(member instanceof PsiMethod) ||
ApproximateResolver.canHaveArgCount((PsiMethod)member, argCount))) {
candidates.add(member);
}
return count.incrementAndGet() < 42;
};
PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
GlobalSearchScope scope = ResolveScopeManager.getInstance(project).getDefaultResolveScope(placeFile);
if (isCall) {
if (!cache.processMethodsWithName(referenceName, processor, scope, null)) {
markExpensive(project);
return null;
}
}
else {
PsiPackage pkg = JavaPsiFacade.getInstance(project).findPackage(referenceName);
if (pkg != null && pkg.getDirectories(scope).length > 0) return null;
if (!cache.processFieldsWithName(referenceName, processor, scope, null) ||
!cache.processClassesWithName(referenceName, processor, scope, null)) {
markExpensive(project);
return null;
}
}
return candidates;
}
private static boolean canBeAccessible(VirtualFile placeFile, PsiMember member) {
return !member.hasModifierProperty(PsiModifier.PRIVATE) || placeFile.equals(PsiUtilCore.getVirtualFile(member));
}
private boolean isExpensive(Project project) {
Set<ReferenceChainLink> expensive = project.getUserData(EXPENSIVE_LINKS);
return expensive != null && expensive.contains(this);
}
private void markExpensive(Project project) {
Set<ReferenceChainLink> expensive = project.getUserData(EXPENSIVE_LINKS);
if (expensive == null) {
project.putUserData(EXPENSIVE_LINKS, expensive = ContainerUtil.newConcurrentSet());
}
expensive.add(this);
}
public List<? extends PsiMember> getSymbolMembers(Set<PsiClass> qualifiers) {
return isCall ? ApproximateResolver.getPossibleMethods(qualifiers, referenceName, argCount)
: ApproximateResolver.getPossibleNonMethods(qualifiers, referenceName);
}
}

View File

@@ -18,16 +18,16 @@ package com.intellij.psi.impl.search;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Set;
import java.util.*;
/**
* @author peter
*/
class ApproximateResolver {
public class ApproximateResolver {
/**
* Tries to calculate the type of a given expression using lightweight resolve: no type inference for method calls, no exact candidate selection, works only with raw types.
* If it returns a non-empty set, the "real" type class is guaranteed to be present in that set. This method can be used to quickly
@@ -43,31 +43,37 @@ class ApproximateResolver {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiTypeCastExpression) return extractClass(expression.getType());
return expression instanceof PsiTypeCastExpression ? extractClass(expression.getType()) :
expression instanceof PsiMethodCallExpression ? getCallType(expression, maxDepth) :
expression instanceof PsiReferenceExpression ? getNonCallType((PsiReferenceExpression)expression, maxDepth) :
null;
}
boolean method = false;
if (expression instanceof PsiMethodCallExpression) {
method = true;
expression = ((PsiMethodCallExpression)expression).getMethodExpression();
@Nullable
private static Set<PsiClass> getCallType(@NotNull PsiExpression expression, int maxDepth) {
PsiReferenceExpression ref = ((PsiMethodCallExpression)expression).getMethodExpression();
PsiExpression qualifier = ref.getQualifierExpression();
if (qualifier == null) return extractClass(expression.getType());
Set<PsiClass> qualifierType = getPossibleTypes(qualifier, maxDepth - 1);
String refName = ref.getReferenceName();
int argCount = ((PsiMethodCallExpression)expression).getArgumentList().getExpressions().length;
List<PsiMethod> methods = refName == null || qualifierType == null ? null : getPossibleMethods(qualifierType, refName, argCount);
return methods == null ? null : getDefiniteSymbolTypes(methods);
}
@Nullable
private static Set<PsiClass> getNonCallType(@NotNull PsiReferenceExpression expression, int maxDepth) {
PsiExpression qualifier = expression.getQualifierExpression();
if (qualifier == null) {
PsiElement target = expression.resolve();
return target instanceof PsiClass ? Collections.singleton((PsiClass)target) : extractClass(expression.getType());
}
if (!(expression instanceof PsiReferenceExpression)) return null;
String name = ((PsiReferenceExpression)expression).getReferenceName();
if (name == null) return null;
PsiExpression qualifier = ((PsiReferenceExpression)expression).getQualifierExpression();
if (qualifier != null) {
Set<PsiClass> qualifierType = getPossibleTypes(qualifier, maxDepth - 1);
if (qualifierType == null) return null;
return method ? getPossibleCallTypes(qualifierType, name) : getPossibleMemberTypes(qualifierType, name);
}
if (method) return null;
PsiElement target = ((PsiReferenceExpression)expression).resolve();
return target instanceof PsiClass ? Collections.singleton((PsiClass)target) : extractClass(expression.getType());
Set<PsiClass> qualifierType = getPossibleTypes(qualifier, maxDepth - 1);
String refName = expression.getReferenceName();
List<? extends PsiMember> members = refName == null || qualifierType == null ? null : getPossibleNonMethods(qualifierType, refName);
return members == null ? null : getDefiniteSymbolTypes(members);
}
@Nullable
@@ -76,32 +82,54 @@ class ApproximateResolver {
return psiClass == null || psiClass instanceof PsiTypeParameter ? null : Collections.singleton(psiClass);
}
@Nullable
private static Set<PsiClass> getPossibleCallTypes(Set<PsiClass> inClasses, @NotNull String name) {
Set<PsiClass> allTypes = ContainerUtil.newHashSet();
for (PsiClass aClass : inClasses) {
for (PsiMethod method : aClass.findMethodsByName(name, true)) {
PsiClass type = PsiUtil.resolveClassInClassTypeOnly(method.getReturnType());
if (type == null || type instanceof PsiTypeParameter) return null;
allTypes.add(type);
}
@NotNull
public static List<PsiMethod> getPossibleMethods(@NotNull Set<PsiClass> symbols, @NotNull String name, int callArgCount) {
return JBIterable.from(symbols).
flatMap(sym -> Arrays.asList(sym.findMethodsByName(name, true))).
filter(m -> canHaveArgCount(m, callArgCount)).
toList();
}
@NotNull
public static List<PsiMember> getPossibleNonMethods(@NotNull Set<PsiClass> symbols, @NotNull String name) {
List<PsiMember> result = new ArrayList<>();
for (PsiClass sym : symbols) {
ContainerUtil.addIfNotNull(result, sym.findFieldByName(name, true));
ContainerUtil.addIfNotNull(result, sym.findInnerClassByName(name, true));
}
return allTypes;
return result;
}
@Nullable
private static Set<PsiClass> getPossibleMemberTypes(Set<PsiClass> inClasses, @NotNull String name) {
Set<PsiClass> allTypes = ContainerUtil.newHashSet();
for (PsiClass aClass : inClasses) {
PsiField field = aClass.findFieldByName(name, true);
if (field != null) {
PsiClass fieldType = PsiUtil.resolveClassInClassTypeOnly(field.getType());
if (fieldType == null || fieldType instanceof PsiTypeParameter) return null;
allTypes.add(fieldType);
public static Set<PsiClass> getDefiniteSymbolTypes(@NotNull List<? extends PsiMember> candidates) {
Set<PsiClass> possibleTypes = new HashSet<>();
for (PsiMember candidate : candidates) {
if (candidate instanceof PsiClass) {
possibleTypes.add((PsiClass)candidate);
}
else if (candidate instanceof PsiMethod && ((PsiMethod)candidate).isConstructor()) {
ContainerUtil.addIfNotNull(possibleTypes, candidate.getContainingClass());
}
else {
//noinspection ConstantConditions
PsiType type = candidate instanceof PsiField ? ((PsiField)candidate).getType() : ((PsiMethod)candidate).getReturnType();
if (type instanceof PsiPrimitiveType) continue;
if (type instanceof PsiArrayType) {
type = PsiType.getJavaLangObject(candidate.getManager(), candidate.getResolveScope());
}
ContainerUtil.addIfNotNull(allTypes, aClass.findInnerClassByName(name, true));
PsiClass typeClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (typeClass == null || typeClass instanceof PsiTypeParameter) {
return null;
}
possibleTypes.add(typeClass);
}
}
return allTypes;
return possibleTypes;
}
public static boolean canHaveArgCount(PsiMethod method, int argCount) {
return method.isVarArgs() ? argCount >= method.getParameterList().getParametersCount() - 1
: argCount == method.getParameterList().getParametersCount();
}
}

View File

@@ -15,9 +15,7 @@
*/
package com.intellij.psi.impl.search;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.QueryExecutorBase;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
@@ -30,88 +28,148 @@ import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.FunExprOccurrence;
import com.intellij.psi.impl.java.JavaFunctionalExpressionIndex;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey.CallLocation;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey.Location;
import com.intellij.psi.impl.java.stubs.JavaMethodElementType;
import com.intellij.psi.impl.java.stubs.index.JavaMethodParameterTypesIndex;
import com.intellij.psi.search.*;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.search.searches.FunctionalExpressionSearch;
import com.intellij.psi.search.searches.FunctionalExpressionSearch.SearchParameters;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.stubs.StubIndexKey;
import com.intellij.psi.util.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PairProcessor;
import com.intellij.util.Processor;
import com.intellij.util.Processors;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.JBIterable;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.atomic.AtomicInteger;
import static com.intellij.util.ObjectUtils.assertNotNull;
public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunctionalExpression, FunctionalExpressionSearch.SearchParameters> {
public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunctionalExpression, SearchParameters> {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.search.JavaFunctionalExpressionSearcher");
/**
* The least number of candidate files with functional expressions that directly scanning them becomes expensive
* and more advanced ways of searching become necessary: e.g. first searching for methods where the functional interface class is used
* and then for their usages,
*/
public static final int SMART_SEARCH_THRESHOLD = 5;
private static final java.util.HashSet<String> KNOWN_STREAM_CLASSES =
ContainerUtil.newHashSet(Stream.class.getName(),
IntStream.class.getName(), DoubleStream.class.getName(), LongStream.class.getName());
@Override
public void processQuery(@NotNull FunctionalExpressionSearch.SearchParameters queryParameters,
@NotNull Processor<PsiFunctionalExpression> consumer) {
final Set<Module> highLevelModules;
final List<PsiClass> funInterfaces;
try (AccessToken ignored = ReadAction.start()) {
PsiClass aClass = queryParameters.getElementToSearch();
if (!aClass.isValid() ||
!aClass.isInterface() ||
InjectedLanguageManager.getInstance(aClass.getProject()).isInjectedFragment(aClass.getContainingFile())) {
return;
}
highLevelModules = getJava8Modules(aClass.getProject());
if (highLevelModules.isEmpty()) return;
funInterfaces = ContainerUtil.filter(processSubInterfaces(aClass), LambdaUtil::isFunctionalClass);
}
for (PsiClass funInterface : funInterfaces) {
SamDescriptor descriptor = ReadAction.compute(() -> {
if (!funInterface.isValid()) return null;
final PsiMethod functionalInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(funInterface);
LOG.assertTrue(functionalInterfaceMethod != null);
final PsiType samType = functionalInterfaceMethod.getReturnType();
if (samType == null) return null;
return new SamDescriptor(funInterface, functionalInterfaceMethod, samType,
convertToGlobalScope(funInterface.getProject(),
funInterface.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope())));
});
if (descriptor == null || !descriptor.search(consumer, highLevelModules)) return;
public void processQuery(@NotNull SearchParameters p, @NotNull Processor<PsiFunctionalExpression> consumer) {
List<SamDescriptor> descriptors = calcDescriptors(p);
AtomicInteger exprCount = new AtomicInteger();
AtomicInteger fileCount = new AtomicInteger();
processOffsets(descriptors, (file, offsets) -> {
fileCount.incrementAndGet();
exprCount.addAndGet(offsets.size());
return processFile(consumer, descriptors, file, offsets);
});
if (exprCount.get() > 0) {
LOG.debug("Loaded " + exprCount.get() + " fun-expressions in " + fileCount.get() + " files");
}
}
@TestOnly
public static Set<VirtualFile> getFilesToSearchInPsi(PsiClass samClass) {
Set<VirtualFile> result = new HashSet<>();
processOffsets(calcDescriptors(new SearchParameters(samClass, samClass.getUseScope())), (file, offsets) -> result.add(file));
return result;
}
@NotNull
private static List<SamDescriptor> calcDescriptors(@NotNull SearchParameters queryParameters) {
List<SamDescriptor> descriptors = new ArrayList<>();
ReadAction.run(() -> {
PsiClass aClass = queryParameters.getElementToSearch();
if (!aClass.isValid() || !aClass.isInterface()) {
return;
}
Project project = aClass.getProject();
if (InjectedLanguageManager.getInstance(project).isInjectedFragment(aClass.getContainingFile()) || !hasJava8Modules(project)) {
return;
}
for (PsiClass samClass : processSubInterfaces(aClass)) {
if (LambdaUtil.isFunctionalClass(samClass)) {
PsiMethod saMethod = assertNotNull(LambdaUtil.getFunctionalInterfaceMethod(samClass));
PsiType samType = saMethod.getReturnType();
if (samType == null) continue;
SearchScope scope = samClass.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope());
descriptors.add(new SamDescriptor(samClass, saMethod, samType, convertToGlobalScope(project, scope)));
}
}
});
return descriptors;
}
@NotNull
private static Set<VirtualFile> getLikelyFiles(List<SamDescriptor> descriptors) {
return JBIterable.from(descriptors).flatMap(SamDescriptor::getMostLikelyFiles).toSet();
}
@NotNull
private static MultiMap<VirtualFile, FunExprOccurrence> getAllOccurrences(List<SamDescriptor> descriptors) {
MultiMap<VirtualFile, FunExprOccurrence> result = MultiMap.createLinkedSet();
for (SamDescriptor descriptor : descriptors) {
ReadAction.run(() -> {
for (FunctionalExpressionKey key : descriptor.generateKeys()) {
FileBasedIndex.getInstance().processValues(JavaFunctionalExpressionIndex.INDEX_ID, key, null, (file, infos) -> {
ProgressManager.checkCanceled();
result.putValues(file, infos);
return true;
}, new JavaSourceFilterScope(descriptor.useScope));
}
});
}
LOG.debug("Found " + result.values().size() + " fun-expressions in " + result.keySet().size() + " files");
return result;
}
private static void processOffsets(List<SamDescriptor> descriptors, PairProcessor<VirtualFile, List<Integer>> processor) {
if (descriptors.isEmpty()) return;
List<PsiClass> samClasses = ContainerUtil.map(descriptors, d -> d.samClass);
MultiMap<VirtualFile, FunExprOccurrence> allCandidates = getAllOccurrences(descriptors);
for (VirtualFile vFile : putLikelyFilesFirst(descriptors, allCandidates.keySet())) {
List<FunExprOccurrence> toLoad = filterInapplicable(samClasses, vFile, allCandidates.get(vFile));
if (!toLoad.isEmpty()) {
LOG.trace("To load " + vFile.getPath() + " with values: " + toLoad);
if (!processor.process(vFile, ContainerUtil.map(toLoad, it -> it.funExprOffset))) {
return;
}
}
}
}
@NotNull
private static Set<VirtualFile> putLikelyFilesFirst(List<SamDescriptor> descriptors, Set<VirtualFile> allFiles) {
Set<VirtualFile> orderedFiles = new LinkedHashSet<>(allFiles);
orderedFiles.retainAll(getLikelyFiles(descriptors));
orderedFiles.addAll(allFiles);
return orderedFiles;
}
@NotNull
private static List<FunExprOccurrence> filterInapplicable(List<PsiClass> samClasses,
VirtualFile vFile,
Collection<FunExprOccurrence> occurrences) {
return ReadAction.compute(() -> ContainerUtil.filter(occurrences, it -> it.canHaveType(samClasses, vFile)));
}
private static boolean processFile(@NotNull Processor<PsiFunctionalExpression> consumer,
PsiClass samClass,
List<SamDescriptor> descriptors,
VirtualFile vFile, Collection<Integer> offsets) {
return ReadAction.compute(() -> {
if (!samClass.isValid()) return true;
PsiFile file = samClass.getManager().findFile(vFile);
PsiFile file = descriptors.get(0).samClass.getManager().findFile(vFile);
if (!(file instanceof PsiJavaFile)) {
LOG.error("Non-java file " + file + "; " + vFile);
return true;
@@ -124,7 +182,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
continue;
}
if (hasType(samClass, expression) && !consumer.process(expression)) {
if (hasType(descriptors, expression) && !consumer.process(expression)) {
return false;
}
}
@@ -133,205 +191,45 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
});
}
private static boolean hasType(PsiClass samClass, PsiFunctionalExpression expression) {
if (!canHaveType(expression, samClass)) return false;
private static boolean hasType(List<SamDescriptor> descriptors, PsiFunctionalExpression expression) {
if (!canHaveType(expression, ContainerUtil.map(descriptors, d -> d.samClass))) return false;
return InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType()), samClass, true);
PsiClass actualClass = PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType());
return ContainerUtil.exists(descriptors, d -> InheritanceUtil.isInheritorOrSelf(actualClass, d.samClass, true));
}
private static boolean canHaveType(PsiFunctionalExpression expression, PsiClass samClass) {
private static boolean canHaveType(PsiFunctionalExpression expression, List<PsiClass> samClasses) {
PsiElement parent = expression.getParent();
if (parent instanceof PsiExpressionList && parent.getParent() instanceof PsiMethodCallExpression) {
int argIndex = Arrays.asList(((PsiExpressionList)parent).getExpressions()).indexOf(expression);
PsiExpression[] args = ((PsiExpressionList)parent).getExpressions();
int argIndex = Arrays.asList(args).indexOf(expression);
PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)parent.getParent()).getMethodExpression();
PsiExpression qualifier = methodExpression.getQualifierExpression();
String methodName = methodExpression.getReferenceName();
if (qualifier != null && methodName != null && argIndex >= 0) {
Set<PsiClass> approximateTypes = ApproximateResolver.getPossibleTypes(qualifier, 10);
return approximateTypes == null || hasMethodWithSamCompatibleParameter(approximateTypes, methodName, argIndex, samClass);
List<PsiMethod> methods = approximateTypes == null ? null :
ApproximateResolver.getPossibleMethods(approximateTypes, methodName, args.length);
return methods == null ||
ContainerUtil.exists(methods, m -> FunExprOccurrence.hasCompatibleParameter(m, argIndex, samClasses));
}
}
return true;
}
private static boolean hasMethodWithSamCompatibleParameter(Set<PsiClass> qualifierClasses, String methodName, int argIndex, PsiClass samClass) {
for (PsiClass qualifierClass : qualifierClasses) {
for (PsiMethod method : qualifierClass.findMethodsByName(methodName, true)) {
PsiParameter[] parameters = method.getParameterList().getParameters();
int paramIndex = method.isVarArgs() ? Math.min(argIndex, parameters.length - 1) : argIndex;
if (paramIndex < parameters.length && canPassFunctionalExpression(samClass, parameters[paramIndex])) {
return true;
}
}
}
return false;
}
@NotNull
private static MultiMap<VirtualFile, Integer> getCandidateOffsets(SamDescriptor descriptor, MultiMap<Location, GlobalSearchScope> queries, Set<VirtualFile> likelyFiles) {
MultiMap<VirtualFile, Integer> result = MultiMap.createLinked();
MultiMap<VirtualFile, Integer> unlikely = MultiMap.createLinked();
for (Location location : queries.keySet()) {
MultiMap<VirtualFile, Integer> offsets = getOffsetsForLocation(descriptor, location, queries.get(location));
for (VirtualFile file : offsets.keySet()) {
(likelyFiles.contains(file) ? result : unlikely).putValues(file, offsets.get(file));
}
if (LOG.isDebugEnabled()) {
logIfLarge(offsets, location);
}
}
result.putAllValues(unlikely);
if (LOG.isDebugEnabled()) {
LOG.debug("checking " + result.values().size() + " fun-expressions in " + result.keySet().size() + " files");
}
return result;
}
private static void logIfLarge(MultiMap<VirtualFile, Integer> offsets, Location location) {
int delta = offsets.values().size();
if (delta > 5) {
String sample = offsets.entrySet().stream().limit(10).map(e -> e.getKey().getName() + "->" + e.getValue()).collect(Collectors.joining(", "));
LOG.debug(delta + " expressions for " + location + "; " + sample);
}
}
@NotNull
private static MultiMap<VirtualFile, Integer> getOffsetsForLocation(SamDescriptor descriptor, Location location, Collection<GlobalSearchScope> scopes) {
MultiMap<VirtualFile, Integer> map = MultiMap.create();
ReadAction.run(() -> {
ProgressManager.checkCanceled();
GlobalSearchScope combinedScope = descriptor.useScope.intersectWith(
GlobalSearchScope.union(scopes.toArray(new GlobalSearchScope[0])));
for (FunctionalExpressionKey key : descriptor.generateKeys(location)) {
FileBasedIndex.getInstance().processValues(JavaFunctionalExpressionIndex.INDEX_ID, key, null, (file, offsets) -> {
for (int i : offsets.toNativeArray()) {
map.putValue(file, i);
}
return true;
}, new JavaSourceFilterScope(combinedScope));
}
});
return map;
}
@NotNull
private static MultiMap<Location, GlobalSearchScope> collectQueryKeys(SamDescriptor descriptor, Iterable<PsiMethod> methods) {
MultiMap<Location, GlobalSearchScope> queries = MultiMap.createSet();
queries.putValue(Location.UNKNOWN, descriptor.useScope);
queries.putValue(new FunctionalExpressionKey.TypedLocation(descriptor.getClassName()), descriptor.useScope);
for (PsiMethod psiMethod : methods) {
ReadAction.run(() -> {
if (!psiMethod.isValid()) return;
GlobalSearchScope methodUseScope = getGlobalUseScope(psiMethod);
for (Location location : getPossibleCallLocations(descriptor.samClass, psiMethod)) {
queries.putValue(location, methodUseScope);
}
});
}
return queries;
}
@NotNull
private static GlobalSearchScope getGlobalUseScope(PsiMethod method) {
if (PsiTreeUtil.getContextOfType(method, PsiAnonymousClass.class) != null || method.hasModifierProperty(PsiModifier.PRIVATE)) {
// don't call method.getUseScope as it'll be too specific and might cause stub-AST switch
VirtualFile vFile = PsiUtilCore.getVirtualFile(method);
if (vFile != null) {
return GlobalSearchScope.fileScope(method.getProject(), vFile);
}
}
return convertToGlobalScope(method.getProject(), method.getUseScope());
}
@NotNull
private static Set<Location> getPossibleCallLocations(PsiClass samClass, PsiMethod calledMethod) {
Set<Location> keys = new HashSet<>();
String samName = samClass.getQualifiedName();
boolean includeStreamApi = samName != null && samName.startsWith("java.util.function.") ||
hasStreamLikeApi(samClass.getProject());
String methodName = calledMethod.getName();
PsiParameter[] parameters = calledMethod.getParameterList().getParameters();
for (int paramIndex = 0; paramIndex < parameters.length; paramIndex++) {
PsiParameter parameter = parameters[paramIndex];
if (canPassFunctionalExpression(samClass, parameter)) {
for (int argCount : getPossibleArgCounts(parameters, paramIndex)) {
for (int argIndex : getPossibleArgIndices(parameter, paramIndex, argCount)) {
keys.add(new CallLocation(methodName, argCount, argIndex, false));
if (includeStreamApi) {
keys.add(new CallLocation(methodName, argCount, argIndex, true));
}
}
}
}
}
return keys;
}
private static int[] getPossibleArgCounts(PsiParameter[] parameters, int paramIndex) {
if (parameters[parameters.length - 1].isVarArgs()) {
return IntStream
.rangeClosed(parameters.length - 1, CallLocation.MAX_ARG_COUNT)
.filter(i -> i > paramIndex)
.toArray();
}
return new int[]{Math.min(parameters.length, CallLocation.MAX_ARG_COUNT)};
}
private static int[] getPossibleArgIndices(PsiParameter parameter, int paramIndex, int argCount) {
if (parameter.isVarArgs()) {
return IntStream
.rangeClosed(paramIndex + 1, CallLocation.MAX_ARG_COUNT)
.filter(i -> i < argCount)
.toArray();
}
return new int[]{Math.min(paramIndex, CallLocation.MAX_ARG_COUNT)};
}
@NotNull
private static Set<Module> getJava8Modules(Project project) {
private static boolean hasJava8Modules(Project project) {
final boolean projectLevelIsHigh = PsiUtil.getLanguageLevel(project).isAtLeast(LanguageLevel.JDK_1_8);
final Set<Module> highLevelModules = new HashSet<>();
for (Module module : ModuleManager.getInstance(project).getModules()) {
final LanguageLevelModuleExtension extension = ModuleRootManager.getInstance(module).getModuleExtension(LanguageLevelModuleExtension.class);
if (extension != null) {
final LanguageLevel level = extension.getLanguageLevel();
if (level == null && projectLevelIsHigh || level != null && level.isAtLeast(LanguageLevel.JDK_1_8)) {
highLevelModules.add(module);
return true;
}
}
}
return highLevelModules;
}
@NotNull
private static Set<String> collectMethodNamesCalledWithFunExpressions(SamDescriptor descriptor) {
Set<String> usedMethodNames = new HashSet<>();
ReadAction.run(() -> FileBasedIndex.getInstance().processAllKeys(JavaFunctionalExpressionIndex.INDEX_ID, key -> {
ProgressManager.checkCanceled();
if (key.canRepresent(descriptor.samParamCount, descriptor.booleanCompatible, descriptor.isVoid) &&
key.location instanceof CallLocation) {
usedMethodNames.add(((CallLocation)key.location).methodName);
}
return true;
}, descriptor.useScope, null));
return usedMethodNames;
}
@Nullable
private static GlobalSearchScope combineResolveScopes(Set<Module> candidateModules, PsiClass samClass) {
List<GlobalSearchScope> scopes = candidateModules.stream()
.map(GlobalSearchScope::moduleWithDependenciesAndLibrariesScope)
.filter(s -> PsiSearchScopeUtil.isInScope(s, samClass))
.collect(Collectors.toList());
return scopes.isEmpty() ? null : GlobalSearchScope.union(scopes.toArray(new GlobalSearchScope[scopes.size()]));
return false;
}
@NotNull
@@ -351,19 +249,6 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
return scope;
}
private static boolean canPassFunctionalExpression(PsiClass sam, PsiParameter parameter) {
PsiType paramType = parameter.getType();
if (paramType instanceof PsiEllipsisType) {
paramType = ((PsiEllipsisType)paramType).getComponentType();
}
PsiClass functionalCandidate = PsiUtil.resolveClassInClassTypeOnly(paramType);
if (functionalCandidate instanceof PsiTypeParameter) {
return InheritanceUtil.isInheritorOrSelf(sam, PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(paramType)), true);
}
return InheritanceUtil.isInheritorOrSelf(functionalCandidate, sam, true);
}
private static Set<PsiClass> processSubInterfaces(PsiClass base) {
Set<PsiClass> result = new HashSet<>();
new Object() {
@@ -396,120 +281,52 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
this.isVoid = PsiType.VOID.equals(samType);
}
@NotNull
private MultiMap<VirtualFile, Integer> getOffsets(Set<Module> highLevelModules) {
GlobalSearchScope visibleFromCandidates = ReadAction.compute(
() -> samClass.isValid() ? combineResolveScopes(highLevelModules, samClass) : null);
if (visibleFromCandidates == null) return MultiMap.empty();
Set<String> usedMethodNames = collectMethodNamesCalledWithFunExpressions(this);
Set<PsiMethod> exactTypeMethods = getMethodsWithParameterType(usedMethodNames, useScope.intersectWith(visibleFromCandidates), getClassName());
Set<PsiMethod> genericMethods = getMethodsWithParameterType(usedMethodNames, visibleFromCandidates, JavaMethodElementType.TYPE_PARAMETER_PSEUDO_NAME);
LOG.debug("#methods: " + (exactTypeMethods.size() + genericMethods.size()));
MultiMap<Location, GlobalSearchScope> queries = collectQueryKeys(this, ContainerUtil.concat(exactTypeMethods, genericMethods));
return getCandidateOffsets(this, queries, getMostLikelyFiles(exactTypeMethods));
}
private boolean search(@NotNull Processor<PsiFunctionalExpression> consumer, Set<Module> highLevelModules) {
for (Map.Entry<VirtualFile, Collection<Integer>> entry : getOffsets(highLevelModules).entrySet()) {
if (!processFile(consumer, samClass, entry.getKey(), entry.getValue())) {
return false;
List<FunctionalExpressionKey> generateKeys() {
List<FunctionalExpressionKey> result = new ArrayList<>();
for (String lambdaType : new String[]{assertNotNull(samClass.getName()), ""}) {
for (int lambdaParamCount : new int[]{FunctionalExpressionKey.UNKNOWN_PARAM_COUNT, samParamCount}) {
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.UNKNOWN, lambdaType));
if (isVoid) {
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.VOID, lambdaType));
} else {
if (booleanCompatible) {
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.BOOLEAN, lambdaType));
}
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.NON_VOID, lambdaType));
}
}
}
return true;
return result;
}
@NotNull
private Set<VirtualFile> getMostLikelyFiles(Set<PsiMethod> exactTypeMethods) {
private Set<VirtualFile> getMostLikelyFiles() {
Set<VirtualFile> files = ContainerUtil.newLinkedHashSet();
ReadAction.run(() -> {
if (!samClass.isValid()) return;
Set<String> likelyNames = ContainerUtil.newLinkedHashSet(getClassName());
for (PsiMethod method : exactTypeMethods) {
likelyNames.add(method.getName());
}
String className = samClass.getName();
Project project = samClass.getProject();
if (className == null) return;
PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(samClass.getProject());
Set<String> likelyNames = ContainerUtil.newLinkedHashSet(className);
StubIndex.getInstance().processElements(JavaMethodParameterTypesIndex.getInstance().getKey(), className,
project, useScope, PsiMethod.class, method -> {
ProgressManager.checkCanceled();
likelyNames.add(method.getName());
return true;
});
PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(project);
Processor<VirtualFile> processor = Processors.cancelableCollectProcessor(files);
for (String name : likelyNames) {
helper.processFilesWithText(this.useScope, UsageSearchContext.IN_CODE, true, name, processor);
for (String word : likelyNames) {
helper.processFilesWithText(useScope, UsageSearchContext.IN_CODE, true, word, processor);
}
});
return files;
}
private Set<PsiMethod> getMethodsWithParameterType(Set<String> usedMethodNames,
GlobalSearchScope scope,
String type) {
Set<PsiMethod> methods = new HashSet<>();
ReadAction.run(() -> {
if (!samClass.isValid()) return;
StubIndexKey<String, PsiMethod> key = JavaMethodParameterTypesIndex.getInstance().getKey();
StubIndex.getInstance().processElements(key, type, samClass.getProject(), scope, PsiMethod.class, method -> {
ProgressManager.checkCanceled();
if (usedMethodNames.contains(method.getName())) {
methods.add(method);
}
return true;
});
});
return methods;
}
@NotNull
String getClassName() {
return assertNotNull(samClass.getName());
}
List<FunctionalExpressionKey> generateKeys(Location location) {
List<FunctionalExpressionKey> result = new ArrayList<>();
for (int lambdaParamCount : new int[]{FunctionalExpressionKey.UNKNOWN_PARAM_COUNT, samParamCount}) {
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.UNKNOWN, location));
if (isVoid) {
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.VOID, location));
} else {
if (booleanCompatible) {
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.BOOLEAN, location));
}
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.NON_VOID, location));
}
}
return result;
}
}
@VisibleForTesting
public static boolean hasStreamLikeApi(final Project project) {
return CachedValuesManager.getManager(project).getCachedValue(project, () ->
CachedValueProvider.Result.create(hasStreamLikeApi(project, "Arrays", "stream") || hasStreamLikeApi(project, "Stream", "of"),
PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT));
}
private static boolean hasStreamLikeApi(Project project, String qualifier, String methodName) {
PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
for (PsiClass candidate : cache.getClassesByName(qualifier, GlobalSearchScope.allScope(project))) {
if (hasMethodWithNonStreamType(methodName, candidate)) return true;
}
for (PsiField field : cache.getFieldsByName(qualifier, GlobalSearchScope.allScope(project))) {
PsiClass fieldType = PsiUtil.resolveClassInClassTypeOnly(field.getType());
if (fieldType == null || fieldType instanceof PsiTypeParameter || hasMethodWithNonStreamType(methodName, fieldType)) return true;
}
return false;
}
private static boolean hasMethodWithNonStreamType(@NotNull String methodName, @NotNull PsiClass candidate) {
for (PsiMethod method : candidate.findMethodsByName(methodName, true)) {
PsiClass returnType = PsiUtil.resolveClassInClassTypeOnly(method.getReturnType());
if (returnType == null || !KNOWN_STREAM_CLASSES.contains(returnType.getQualifiedName())) {
return true;
}
}
return false;
}
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2000-2016 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 com.intellij.psi.impl.java;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
import com.intellij.psi.impl.source.JavaLightTreeUtil;
import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
/**
* @author peter
*/
public class StreamApiDetector {
public static final Set<String> STREAM_INTERMEDIATE_METHODS = ContainerUtil.newHashSet("filter",
"map", "mapToInt", "mapToLong", "mapToDouble",
"flatMap", "flatMapToInt", "flatMapToLong", "flatMapToDouble",
"distinct", "sorted", "peek", "limit", "skip",
"forEach", "forEachOrdered",
"reduce", "collect", "max",
"anyMatch", "allMatch", "noneMatch",
"findFirst", "findAny");
static boolean isStreamApiCall(LighterAST tree, LighterASTNode methodCall) {
LighterASTNode methodExpr = tree.getChildren(methodCall).get(0);
String name = JavaLightTreeUtil.getNameIdentifierText(tree, methodExpr);
if (STREAM_INTERMEDIATE_METHODS.contains(name)) {
LighterASTNode qualifier = tree.getChildren(methodExpr).get(0);
return qualifier.getTokenType() == METHOD_CALL_EXPRESSION && isStreamApiCall(tree, qualifier);
}
return isRootCall(tree, methodCall, methodExpr, name);
}
private static boolean isRootCall(LighterAST tree, LighterASTNode methodCall, LighterASTNode methodExpr, String name) {
if ("stream".equals(name) || "parallelStream".equals(name)) {
List<LighterASTNode> argList = JavaLightTreeUtil.getArgList(tree, methodCall);
if (argList == null) return false;
return argList.isEmpty() || !argList.isEmpty() && hasQualifier(tree, methodExpr, "Arrays");
}
if ("of".equals(name)) {
List<LighterASTNode> argList = JavaLightTreeUtil.getArgList(tree, methodCall);
return argList != null && !argList.isEmpty() && hasQualifier(tree, methodExpr, "Stream");
}
return false;
}
private static boolean hasQualifier(LighterAST tree, LighterASTNode methodExpr, String refName) {
LighterASTNode node = tree.getChildren(methodExpr).get(0);
return node.getTokenType() == REFERENCE_EXPRESSION &&
refName.equals(JavaLightTreeUtil.getNameIdentifierText(tree, node)) &&
!hasContextVariable(tree, node, refName);
}
private static boolean hasContextVariable(final LighterAST tree, LighterASTNode node, final String varName) {
final AtomicBoolean result = new AtomicBoolean(false);
new RecursiveLighterASTNodeWalkingVisitor(tree) {
@Override
public void visitNode(@NotNull LighterASTNode element) {
if (element.getTokenType() == LOCAL_VARIABLE && varName.equals(JavaLightTreeUtil.getNameIdentifierText(tree, element))) {
result.set(true);
}
super.visitNode(element);
}
}.visitNode(tree.getRoot());
return result.get();
}
}

View File

@@ -16,10 +16,12 @@
package com.intellij.psi.impl.java.stubs;
import com.google.common.base.MoreObjects;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.io.IOUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.DataInput;
import java.io.DataOutput;
@@ -32,47 +34,26 @@ public class FunctionalExpressionKey {
public static final int UNKNOWN_PARAM_COUNT = -1;
private final int lambdaParameterCount;
private final CoarseType lambdaReturnType;
public final Location location;
@NotNull private final String knownType;
public FunctionalExpressionKey(int lambdaParameterCount, @NotNull CoarseType lambdaReturnType, @NotNull Location location) {
this.location = location;
public FunctionalExpressionKey(int lambdaParameterCount, @NotNull CoarseType lambdaReturnType, @Nullable String knownFunExprType) {
this.lambdaParameterCount = lambdaParameterCount;
this.lambdaReturnType = lambdaReturnType;
this.knownType = StringUtil.notNullize(knownFunExprType);
}
@NotNull
public static FunctionalExpressionKey deserializeKey(@NotNull DataInput dataStream) throws IOException {
int parameterCount = dataStream.readByte();
CoarseType type = CoarseType.values()[dataStream.readByte()];
return new FunctionalExpressionKey(parameterCount, type, deserializeLocation(dataStream));
String knownType = IOUtil.readUTF(dataStream);
return new FunctionalExpressionKey(parameterCount, type, knownType);
}
public void serializeKey(@NotNull DataOutput dataStream) throws IOException {
dataStream.writeByte(lambdaParameterCount);
dataStream.writeByte(lambdaReturnType.ordinal());
serializeLocation(dataStream);
}
private static Location deserializeLocation(DataInput dataStream) throws IOException {
byte locationType = dataStream.readByte();
if (locationType == 0) return Location.UNKNOWN;
if (locationType == 1) return CallLocation.deserializeCall(dataStream);
if (locationType == 2) return TypedLocation.deserializeField(dataStream);
throw new AssertionError(locationType);
}
private void serializeLocation(@NotNull DataOutput dataStream) throws IOException {
if (location == Location.UNKNOWN) {
dataStream.writeByte(0);
}
else if (location instanceof CallLocation) {
dataStream.writeByte(1);
((CallLocation)location).serializeCall(dataStream);
}
else if (location instanceof TypedLocation) {
dataStream.writeByte(2);
((TypedLocation)location).serializeVariable(dataStream);
}
IOUtil.writeUTF(dataStream, knownType);
}
public boolean canRepresent(int samParamCount, boolean booleanCompatible, boolean isVoid) {
@@ -99,7 +80,7 @@ public class FunctionalExpressionKey {
if (lambdaParameterCount != key.lambdaParameterCount) return false;
if (lambdaReturnType != key.lambdaReturnType) return false;
if (!location.equals(key.location)) return false;
if (!knownType.equals(key.knownType)) return false;
return true;
}
@@ -108,7 +89,7 @@ public class FunctionalExpressionKey {
public int hashCode() {
int result = lambdaParameterCount;
result = 31 * result + lambdaReturnType.ordinal();
result = 31 * result + location.hashCode();
result = 31 * result + knownType.hashCode();
return result;
}
@@ -117,130 +98,10 @@ public class FunctionalExpressionKey {
return MoreObjects.toStringHelper(this)
.add("lambdaParameterCount", lambdaParameterCount)
.add("type", lambdaReturnType)
.add("location", location)
.add("knownType", knownType)
.toString();
}
public interface Location {
Location UNKNOWN = new Location() {
@Override
public String toString() {
return "UNKNOWN";
}
@Override
public int hashCode() {
return 0;
}
};
}
public static class CallLocation implements Location {
public static final int MAX_ARG_COUNT = 10;
@NotNull public final String methodName;
public final int methodArgsLength;
public final int callArgIndex;
public final boolean streamApi;
public CallLocation(@NotNull String methodName, int methodArgsLength, int callArgIndex, boolean streamApi) {
this.methodName = methodName;
this.methodArgsLength = Math.min(methodArgsLength, MAX_ARG_COUNT);
this.callArgIndex = Math.min(callArgIndex, MAX_ARG_COUNT - 1);
this.streamApi = streamApi;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CallLocation)) return false;
CallLocation location = (CallLocation)o;
if (methodArgsLength != location.methodArgsLength) return false;
if (callArgIndex != location.callArgIndex) return false;
if (!methodName.equals(location.methodName)) return false;
if (streamApi != location.streamApi) return false;
return true;
}
@Override
public int hashCode() {
int result = methodName.hashCode();
result = 31 * result + methodArgsLength;
result = 31 * result + callArgIndex;
result = 31 * result + (streamApi ? 0 : 1);
return result;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("methodName", methodName)
.add("methodArgsLength", methodArgsLength)
.add("callArgIndex", callArgIndex)
.add("streamApi", streamApi)
.toString();
}
@NotNull
private static Location deserializeCall(DataInput dataStream) throws IOException {
String methodName = IOUtil.readUTF(dataStream);
int methodArgsLength = dataStream.readByte();
int argIndex = dataStream.readByte();
boolean streamApi = dataStream.readBoolean();
return new CallLocation(methodName, methodArgsLength, argIndex, streamApi);
}
private void serializeCall(@NotNull DataOutput dataStream) throws IOException {
IOUtil.writeUTF(dataStream, methodName);
dataStream.writeByte(methodArgsLength);
dataStream.writeByte(callArgIndex);
dataStream.writeBoolean(streamApi);
}
}
public static class TypedLocation implements Location {
@NotNull public final String varType;
public TypedLocation(@NotNull String varType) {
this.varType = varType;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TypedLocation)) return false;
TypedLocation location = (TypedLocation)o;
if (!varType.equals(location.varType)) return false;
return true;
}
@Override
public int hashCode() {
return varType.hashCode();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("fieldType", varType)
.toString();
}
public static TypedLocation deserializeField(DataInput dataStream) throws IOException {
return new TypedLocation(IOUtil.readUTF(dataStream));
}
public void serializeVariable(DataOutput dataStream) throws IOException {
IOUtil.writeUTF(dataStream, varType);
}
}
public enum CoarseType {
VOID, UNKNOWN, BOOLEAN, NON_VOID
}

View File

@@ -13,6 +13,6 @@ interface MyStream {
class Test {
{
Arrays.stream().map(p -> "a");
Arrays.stream(1).map(p -> "a");
}
}

View File

@@ -0,0 +1,13 @@
interface I {
int a();
}
class A<Z> {
void foo(I i);
}
class C<X, Y, SR extends A<Y>> {
void bar(SR t) {
t.foo(() -> 1);
}
}

View File

@@ -0,0 +1,5 @@
class C {
{
pkg.p1.p2.p3.Util.foo(() -> {});
}
}

View File

@@ -16,6 +16,7 @@
package com.intellij.codeInsight.daemon.lambda;
import com.intellij.JavaTestUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.search.JavaFunctionalExpressionSearcher;
import com.intellij.psi.search.GlobalSearchScope;
@@ -122,13 +123,55 @@ public class FindFunctionalInterfaceTest extends LightCodeInsightFixtureTestCase
}
public void testStreamOfLikeApiWithField() {
assertFalse(JavaFunctionalExpressionSearcher.hasStreamLikeApi(getProject()));
myFixture.addClass("class Base { StrType Stream = null; }");
configure();
assertTrue(JavaFunctionalExpressionSearcher.hasStreamLikeApi(getProject()));
assertSize(1, FunctionalExpressionSearch.search(findClass("I")).findAll());
}
public void testCallWithQualifiedName() {
myFixture.addClass("package pkg.p1.p2.p3; public interface I { void run() {} }");
myFixture.addClass("package pkg.p1.p2.p3; public class Util { public static void foo(I i) {} }");
configure();
assertSize(1, FunctionalExpressionSearch.search(findClass("pkg.p1.p2.p3.I")).findAll());
}
public void testCallOnGenericParameter() {
configure();
assertSize(1, FunctionalExpressionSearch.search(findClass("I")).findAll());
}
public void testDontVisitInapplicableFiles() {
PsiClass sam = myFixture.addClass("interface I { void foo(); }");
myFixture.addClass("class Some { " +
"{ I i = () -> {}; }" +
"void doTest(int a) {} " +
"void doTest(I i, I j) {} " +
"Some intermediate() {} " +
"Object intermediate(int a, int b) {} " +
"}");
myFixture.addClass("class _WrongSignature {{ I i = a -> {}; I j = () -> true; }}");
myFixture.addClass("class _CallArgumentCountMismatch extends Some {{ " +
" doTest(() -> {}); " +
" intermediate(4).doTest(() -> {}, () -> {}); " +
"}}");
myFixture.addClass("class _KnownTypeVariableAssignment {" +
"static Runnable field;" +
"{ Runnable r = () -> {}; field = () -> {}; } " +
"}");
myFixture.addClass("class _SuperFieldAssignment extends _KnownTypeVariableAssignment {" +
"{ field = () -> {}; } " +
"}");
myFixture.addClass("import static _KnownTypeVariableAssignment.*; " +
"class _StaticallyImportedFieldAssignment {" +
"{ field = () -> {}; } " +
"}");
assertSize(1, FunctionalExpressionSearch.search(sam).findAll());
for (VirtualFile file : JavaFunctionalExpressionSearcher.getFilesToSearchInPsi(sam)) {
assertFalse(file.getName(), file.getName().startsWith("_"));
}
}
private PsiClass findClass(String i) {
return JavaPsiFacade.getInstance(getProject()).findClass(i, GlobalSearchScope.allScope(getProject()));
}

View File

@@ -18,6 +18,7 @@ package org.jetbrains.plugins.groovy.lang.overriding
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.psi.search.searches.FunctionalExpressionSearch
import com.intellij.psi.search.searches.OverridingMethodsSearch
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
@@ -62,4 +63,10 @@ public class FindOverridingMethodsAndClassesTest extends LightCodeInsightFixture
final Collection<PsiClass> classes = ClassInheritorsSearch.search(psiClass).findAll()
assertEquals("Class count is wrong", classCount, classes.size())
}
public void "test find java functional expression passed into a groovy method"() {
myFixture.addFileToProject("a.groovy", "interface I { void foo(); }; class C { static void bar(I i) {}}")
myFixture.addFileToProject("a.java", "class D {{ C.bar(() -> {}); }")
assertSize(1, FunctionalExpressionSearch.search(myFixture.findClass("I")).findAll());
}
}