mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
distinguish boolean lambdas for finer search granularity; make FunctionalExpressionKey#hashCode independent of enum hashCode
This commit is contained in:
+50
-33
@@ -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()
|
||||
|
||||
+39
-11
@@ -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;
|
||||
|
||||
+33
-13
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user