distinguish boolean lambdas for finer search granularity; make FunctionalExpressionKey#hashCode independent of enum hashCode

This commit is contained in:
peter
2016-07-25 11:28:54 +02:00
parent 623d814fdf
commit ec0f90dbbf
5 changed files with 126 additions and 59 deletions
@@ -45,7 +45,6 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Processor;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
@@ -86,7 +85,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
for (PsiClass funInterface : funInterfaces) {
final GlobalSearchScope useScope;
final int expectedFunExprParamsCount;
final boolean isVoid;
final PsiType samType;
try (AccessToken ignored = ReadAction.start()) {
if (!funInterface.isValid()) continue;
@@ -95,11 +94,11 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
final PsiMethod functionalInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(funInterface);
LOG.assertTrue(functionalInterfaceMethod != null);
expectedFunExprParamsCount = functionalInterfaceMethod.getParameterList().getParameters().length;
isVoid = PsiType.VOID.equals(functionalInterfaceMethod.getReturnType());
samType = functionalInterfaceMethod.getReturnType();
}
MultiMap<FunctionalExpressionKey, GlobalSearchScope> queries =
collectQueryKeys(useScope, funInterface, expectedFunExprParamsCount, isVoid, highLevelModules);
collectQueryKeys(useScope, funInterface, expectedFunExprParamsCount, samType, highLevelModules);
for (PsiFunctionalExpression expression : getCandidates(useScope, funInterface.getProject(), queries)) {
if (!processExpression(consumer, funInterface, expression)) {
@@ -154,28 +153,30 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
private static MultiMap<FunctionalExpressionKey, GlobalSearchScope> collectQueryKeys(GlobalSearchScope useScope,
PsiClass aClass,
int samParamCount,
boolean samVoid,
PsiType samType,
Set<Module> candidateModules) {
//collect all methods with parameter of functional interface or free type parameter type
Collection<PsiMethod> methodCandidates = getCandidateMethodsWithSuitableParams(aClass, useScope, candidateModules, samParamCount, samVoid);
MultiMap<FunctionalExpressionKey, GlobalSearchScope> queries = MultiMap.createSet();
for (FunctionalExpressionKey key : generateKeys(samParamCount, samVoid, FunctionalExpressionKey.Location.UNKNOWN)) {
queries.putValue(key, useScope); // check all fun-exprs that aren't inside calls or variables
}
for (FunctionalExpressionKey key : generateKeys(samParamCount, samVoid,
new FunctionalExpressionKey.VariableLocation(assertNotNull(aClass.getName())))) {
queries.putValue(key, useScope);
}
ReadAction.run(() -> {
if (!samType.isValid()) return;
//find all usages of method candidates in files with functional expressions
for (final PsiMethod psiMethod : methodCandidates) {
for (FunctionalExpressionKey key : generateKeys(samParamCount, samType, FunctionalExpressionKey.Location.UNKNOWN)) {
queries.putValue(key, useScope); // check all fun-exprs that aren't inside calls or variables
}
for (FunctionalExpressionKey key : generateKeys(samParamCount, samType,
new FunctionalExpressionKey.VariableLocation(assertNotNull(aClass.getName())))) {
queries.putValue(key, useScope);
}
});
//collect all methods with parameter of functional interface or free type parameter type
for (final PsiMethod psiMethod : getCandidateMethodsWithSuitableParams(aClass, useScope, candidateModules, samParamCount, samType)) {
ReadAction.run(() -> {
if (!psiMethod.isValid()) return;
if (!psiMethod.isValid() || !samType.isValid()) return;
final GlobalSearchScope methodUseScope = convertToGlobalScope(psiMethod.getProject(), psiMethod.getUseScope());
for (FunctionalExpressionKey key : getQueryKeys(aClass, samParamCount, samVoid, psiMethod)) {
for (FunctionalExpressionKey key : getQueryKeys(aClass, samParamCount, samType, psiMethod)) {
queries.putValue(key, methodUseScope);
}
});
@@ -186,7 +187,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
@NotNull
private static Set<FunctionalExpressionKey> getQueryKeys(PsiClass samClass,
int samParamCount,
boolean samVoid,
PsiType samType,
PsiMethod calledMethod) {
Set<FunctionalExpressionKey> keys = new HashSet<>();
@@ -197,7 +198,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
if (canPassFunctionalExpression(samClass, parameter)) {
for (int argCount : getPossibleArgCounts(parameters, paramIndex)) {
for (int argIndex : getPossibleArgIndices(parameter, paramIndex, argCount)) {
keys.addAll(generateKeys(samParamCount, samVoid, new CallLocation(methodName, argCount, argIndex)));
keys.addAll(generateKeys(samParamCount, samType, new CallLocation(methodName, argCount, argIndex)));
}
}
}
@@ -207,12 +208,19 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
}
private static List<FunctionalExpressionKey> generateKeys(int samMethodParamsCount,
boolean samMethodVoid,
PsiType samType,
FunctionalExpressionKey.Location location) {
List<FunctionalExpressionKey> result = new ArrayList<>();
for (int lambdaParamCount : new int[]{FunctionalExpressionKey.UNKNOWN_PARAM_COUNT, samMethodParamsCount}) {
result.add(new FunctionalExpressionKey(lambdaParamCount, ThreeState.UNSURE, location));
result.add(new FunctionalExpressionKey(lambdaParamCount, ThreeState.fromBoolean(samMethodVoid), location));
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.UNKNOWN, location));
if (PsiType.VOID.equals(samType)) {
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.VOID, location));
} else {
if (FunctionalExpressionKey.isBooleanCompatible(samType)) {
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.BOOLEAN, location));
}
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.NON_VOID, location));
}
}
return result;
}
@@ -258,7 +266,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
final GlobalSearchScope useScope,
final Set<Module> candidateModules,
int expectedFunExprParamsCount,
boolean isVoid) {
PsiType samType) {
return ApplicationManager.getApplication().runReadAction(new Computable<Collection<PsiMethod>>() {
@Override
public Collection<PsiMethod> compute() {
@@ -267,14 +275,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
GlobalSearchScope visibleFromCandidates = combineResolveScopes(candidateModules, samClass);
if (visibleFromCandidates == null) return Collections.emptyList();
Set<String> usedMethodNames = new HashSet<>();
StubIndex.getInstance().processAllKeys(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, key -> {
ProgressManager.checkCanceled();
if (key.canRepresent(expectedFunExprParamsCount, isVoid) && key.location instanceof CallLocation) {
usedMethodNames.add(((CallLocation)key.location).methodName);
}
return true;
}, useScope, null);
Set<String> usedMethodNames = collectMethodNamesCalledWithFunExpressions(samType, expectedFunExprParamsCount, useScope);
Set<PsiMethod> methods = ContainerUtil.newLinkedHashSet();
Processor<PsiMethod> methodProcessor = method -> {
@@ -295,6 +296,22 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
});
}
@NotNull
private static Set<String> collectMethodNamesCalledWithFunExpressions(PsiType samType, int samParamCount, GlobalSearchScope useScope) {
boolean booleanCompatible = FunctionalExpressionKey.isBooleanCompatible(samType);
boolean isVoid = PsiType.VOID.equals(samType);
Set<String> usedMethodNames = new HashSet<>();
StubIndex.getInstance().processAllKeys(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, key -> {
ProgressManager.checkCanceled();
if (key.canRepresent(samParamCount, booleanCompatible, isVoid) && key.location instanceof CallLocation) {
usedMethodNames.add(((CallLocation)key.location).methodName);
}
return true;
}, useScope, null);
return usedMethodNames;
}
@Nullable
private static GlobalSearchScope combineResolveScopes(Set<Module> candidateModules, PsiClass samClass) {
List<GlobalSearchScope> scopes = candidateModules.stream()
@@ -21,6 +21,7 @@ import com.intellij.openapi.util.Ref;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiFunctionalExpression;
import com.intellij.psi.impl.cache.RecordUtil;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey.CoarseType;
import com.intellij.psi.impl.java.stubs.index.JavaStubIndexKeys;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.ElementType;
@@ -32,12 +33,12 @@ import com.intellij.psi.stubs.StubInputStream;
import com.intellij.psi.stubs.StubOutputStream;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.ThreeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
@@ -66,7 +67,7 @@ public abstract class FunctionalExpressionElementType<T extends PsiFunctionalExp
public FunctionalExpressionStub<T> createStub(LighterAST tree, LighterASTNode funExpr, StubElement parentStub) {
return new FunctionalExpressionStub<T>(parentStub, this,
new FunctionalExpressionKey(getFunExprParameterCount(tree, funExpr),
isVoid(tree, funExpr),
calcType(tree, funExpr),
calcLocation(tree, funExpr)));
}
@@ -110,32 +111,57 @@ public abstract class FunctionalExpressionElementType<T extends PsiFunctionalExp
return exprList == null ? null : LightTreeUtil.getChildrenOfType(tree, exprList, ElementType.EXPRESSION_BIT_SET);
}
private static ThreeState isVoid(final LighterAST tree, LighterASTNode funExpr) {
if (funExpr.getTokenType() == METHOD_REF_EXPRESSION) return ThreeState.UNSURE;
private static CoarseType calcType(final LighterAST tree, LighterASTNode funExpr) {
if (funExpr.getTokenType() == METHOD_REF_EXPRESSION) return CoarseType.UNKNOWN;
LighterASTNode block = LightTreeUtil.firstChildOfType(tree, funExpr, CODE_BLOCK);
if (block == null) return ThreeState.UNSURE;
if (block == null) return CoarseType.UNKNOWN;
final Ref<ThreeState> isVoid = Ref.create(ThreeState.UNSURE);
final Ref<Boolean> returnsSomething = Ref.create(null);
final AtomicBoolean isBoolean = new AtomicBoolean();
final AtomicBoolean hasStatements = new AtomicBoolean();
new RecursiveLighterASTNodeWalkingVisitor(tree) {
@Override
public void visitNode(@NotNull LighterASTNode element) {
IElementType type = element.getTokenType();
if (type == LAMBDA_EXPRESSION || ElementType.MEMBER_BIT_SET.contains(type) || isVoid.get() != ThreeState.UNSURE) {
if (type == LAMBDA_EXPRESSION || ElementType.MEMBER_BIT_SET.contains(type) || !returnsSomething.isNull()) {
return;
}
if (type == RETURN_STATEMENT) {
boolean noExpr = LightTreeUtil.firstChildOfType(tree, element, ElementType.EXPRESSION_BIT_SET) == null;
isVoid.set(ThreeState.fromBoolean(noExpr));
LighterASTNode expr = LightTreeUtil.firstChildOfType(tree, element, ElementType.EXPRESSION_BIT_SET);
returnsSomething.set(expr != null);
if (isBooleanLiteral(tree, expr)) {
isBoolean.set(true);
}
return;
}
if (ElementType.JAVA_STATEMENT_BIT_SET.contains(type) && type != THROW_STATEMENT) {
hasStatements.set(true);
}
super.visitNode(element);
}
}.visitNode(block);
return isVoid.get();
if (isBoolean.get()) {
return CoarseType.BOOLEAN;
}
if (returnsSomething.isNull()) {
return hasStatements.get() ? CoarseType.VOID : CoarseType.UNKNOWN;
}
return returnsSomething.get() ? CoarseType.NON_VOID : CoarseType.VOID;
}
private static boolean isBooleanLiteral(LighterAST tree, LighterASTNode expr) {
if (expr != null && expr.getTokenType() == LITERAL_EXPRESSION) {
IElementType type = tree.getChildren(expr).get(0).getTokenType();
return type == JavaTokenType.TRUE_KEYWORD || type == JavaTokenType.FALSE_KEYWORD;
}
return false;
}
private static int getFunExprParameterCount(LighterAST tree, LighterASTNode funExpr) {
@@ -199,7 +225,9 @@ public abstract class FunctionalExpressionElementType<T extends PsiFunctionalExp
while (node != null) {
final IElementType type = node.getTokenType();
if (elementType.contains(type)) return node;
if (ElementType.JAVA_STATEMENT_BIT_SET.contains(type) || ElementType.MEMBER_BIT_SET.contains(type)) return null;
if (ElementType.JAVA_STATEMENT_BIT_SET.contains(type) ||
ElementType.MEMBER_BIT_SET.contains(type) ||
ARRAY_INITIALIZER_EXPRESSION == type) return null;
node = tree.getParent(node);
}
return null;
@@ -16,7 +16,8 @@
package com.intellij.psi.impl.java.stubs;
import com.google.common.base.Objects;
import com.intellij.util.ThreeState;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.io.IOUtil;
import org.jetbrains.annotations.NotNull;
@@ -30,25 +31,25 @@ import java.io.IOException;
public class FunctionalExpressionKey {
public static final int UNKNOWN_PARAM_COUNT = -1;
public final int lambdaParameterCount;
public final ThreeState isVoid;
public final CoarseType lambdaType;
public final Location location;
public FunctionalExpressionKey(int lambdaParameterCount, @NotNull ThreeState isVoid, @NotNull Location location) {
public FunctionalExpressionKey(int lambdaParameterCount, @NotNull CoarseType lambdaType, @NotNull Location location) {
this.location = location;
this.lambdaParameterCount = lambdaParameterCount;
this.isVoid = isVoid;
this.lambdaType = lambdaType;
}
@NotNull
public static FunctionalExpressionKey deserializeKey(@NotNull DataInput dataStream) throws IOException {
int parameterCount = dataStream.readByte();
ThreeState voidCompatible = ThreeState.values()[dataStream.readByte()];
return new FunctionalExpressionKey(parameterCount, voidCompatible, deserializeLocation(dataStream));
CoarseType type = CoarseType.values()[dataStream.readByte()];
return new FunctionalExpressionKey(parameterCount, type, deserializeLocation(dataStream));
}
public void serializeKey(@NotNull DataOutput dataStream) throws IOException {
dataStream.writeByte(lambdaParameterCount);
dataStream.writeByte(isVoid.ordinal());
dataStream.writeByte(lambdaType.ordinal());
serializeLocation(dataStream);
}
@@ -74,9 +75,19 @@ public class FunctionalExpressionKey {
}
}
public boolean canRepresent(int samParamCount, boolean samVoid) {
return (samParamCount == lambdaParameterCount || lambdaParameterCount == -1) &&
(isVoid == ThreeState.UNSURE || samVoid == isVoid.toBoolean());
public boolean canRepresent(int samParamCount, boolean booleanCompatible, boolean isVoid) {
if (lambdaParameterCount >= 0 && samParamCount != lambdaParameterCount) return false;
switch (lambdaType) {
case VOID: return isVoid;
case NON_VOID: return !isVoid;
case BOOLEAN: return booleanCompatible;
default: return true;
}
}
public static boolean isBooleanCompatible(PsiType samType) {
return PsiType.BOOLEAN.equals(samType) || TypeConversionUtil.isAssignableFromPrimitiveWrapper(TypeConversionUtil.erasure(samType));
}
@Override
@@ -87,7 +98,7 @@ public class FunctionalExpressionKey {
FunctionalExpressionKey key = (FunctionalExpressionKey)o;
if (lambdaParameterCount != key.lambdaParameterCount) return false;
if (isVoid != key.isVoid) return false;
if (lambdaType != key.lambdaType) return false;
if (!location.equals(key.location)) return false;
return true;
@@ -96,7 +107,7 @@ public class FunctionalExpressionKey {
@Override
public int hashCode() {
int result = lambdaParameterCount;
result = 31 * result + isVoid.hashCode();
result = 31 * result + lambdaType.ordinal();
result = 31 * result + location.hashCode();
return result;
}
@@ -105,13 +116,18 @@ public class FunctionalExpressionKey {
public String toString() {
return Objects.toStringHelper(this)
.add("lambdaParameterCount", lambdaParameterCount)
.add("isVoid", isVoid)
.add("type", lambdaType)
.add("location", location)
.toString();
}
public interface Location {
Location UNKNOWN = new Location() {
@Override
public String toString() {
return "UNKNOWN";
}
@Override
public int hashCode() {
return 0;
@@ -217,4 +233,8 @@ public class FunctionalExpressionKey {
IOUtil.writeUTF(dataStream, varType);
}
}
public enum CoarseType {
VOID, UNKNOWN, BOOLEAN, NON_VOID
}
}
@@ -165,7 +165,9 @@ public abstract class JavaMethodElementType extends JavaStubElementType<PsiMetho
String typeName = PsiNameHelper.getShortClassName(type.text);
if (TypeConversionUtil.isPrimitive(typeName) || TypeConversionUtil.isPrimitiveWrapper(typeName)) continue;
sink.occurrence(JavaStubIndexKeys.METHOD_TYPES, typeName);
if (methodTypeParams.contains(typeName)) {
if (typeName.equals(type.text) &&
(type.arrayCount == 0 || type.arrayCount == 1 && type.isEllipsis) &&
methodTypeParams.contains(typeName)) {
sink.occurrence(JavaStubIndexKeys.METHOD_TYPES, TYPE_PARAMETER_PSEUDO_NAME);
}
}
@@ -38,7 +38,7 @@ import java.io.IOException;
* @author max
*/
public class JavaFileElementType extends ILightStubFileElementType<PsiJavaFileStub> {
public static final int STUB_VERSION = 30;
public static final int STUB_VERSION = 31;
public JavaFileElementType() {
super("java.FILE", JavaLanguage.INSTANCE);