From ded3f46951d68f43689891fc5d5a107aabca0e5a Mon Sep 17 00:00:00 2001 From: peter Date: Fri, 29 Jul 2016 19:42:00 +0200 Subject: [PATCH] lambda search: detect more boolean lambdas, use explicit types in methods and casts, introduce a parameter object in the search --- .../JavaFunctionalExpressionSearcher.java | 169 ++++++++---------- .../com/intellij/psi/PsiBinaryExpression.java | 5 + .../intellij/psi/util/TypeConversionUtil.java | 9 +- .../FunctionalExpressionElementType.java | 71 +++++--- .../java/stubs/FunctionalExpressionKey.java | 18 +- .../psi/impl/source/JavaFileElementType.java | 2 +- .../psi/impl/source/tree/LightTreeUtil.java | 15 ++ 7 files changed, 159 insertions(+), 130 deletions(-) diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaFunctionalExpressionSearcher.java b/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaFunctionalExpressionSearcher.java index 3db71272f0c0..3fc1b91e21ab 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaFunctionalExpressionSearcher.java +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaFunctionalExpressionSearcher.java @@ -33,6 +33,7 @@ import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; 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.impl.java.stubs.index.JavaStubIndexKeys; @@ -64,7 +65,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase { + if (!funInterface.isValid()) return null; final PsiMethod functionalInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(funInterface); LOG.assertTrue(functionalInterfaceMethod != null); - samParamCount = functionalInterfaceMethod.getParameterList().getParameters().length; - samType = functionalInterfaceMethod.getReturnType(); - if (samType == null) continue; - } + final PsiType samType = functionalInterfaceMethod.getReturnType(); + if (samType == null) return null; - MultiMap queries = - collectQueryKeys(useScope, funInterface, samParamCount, samType, highLevelModules); + return new SamDescriptor(funInterface, functionalInterfaceMethod, samType, + convertToGlobalScope(funInterface.getProject(), + funInterface.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope()))); + }); + if (descriptor == null) return; - for (PsiFunctionalExpression expression : getCandidates(useScope, funInterface.getProject(), queries)) { + MultiMap queries = collectQueryKeys(descriptor, highLevelModules); + for (PsiFunctionalExpression expression : getCandidates(descriptor, queries)) { if (!processExpression(consumer, funInterface, expression)) { return; } @@ -130,23 +128,22 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase getCandidates(GlobalSearchScope useScope, - Project project, - MultiMap queries) { + private static Collection getCandidates(SamDescriptor descriptor, MultiMap queries) { MultiMap exprs = MultiMap.createLinked(); - for (Map.Entry> entry : queries.entrySet()) { + for (Map.Entry> entry : queries.entrySet()) { ReadAction.run(() -> { ProgressManager.checkCanceled(); - GlobalSearchScope combinedScope = useScope.intersectWith( + GlobalSearchScope combinedScope = descriptor.useScope.intersectWith( GlobalSearchScope.union(entry.getValue().toArray(new GlobalSearchScope[0]))); - StubIndex.getInstance().processElements(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, - entry.getKey(), - project, combinedScope, null, - PsiFunctionalExpression.class, - expression -> { - exprs.putValue(expression.getContainingFile(), expression); - return true; - }); + for (FunctionalExpressionKey key : descriptor.generateKeys(entry.getKey())) { + StubIndex.getInstance().processElements(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, key, + descriptor.samClass.getProject(), combinedScope, null, + PsiFunctionalExpression.class, + expression -> { + exprs.putValue(expression.getContainingFile(), expression); + return true; + }); + } }); } @@ -156,34 +153,19 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase collectQueryKeys(GlobalSearchScope useScope, - PsiClass aClass, - int samParamCount, - @NotNull PsiType samType, - Set candidateModules) { - MultiMap queries = MultiMap.createSet(); - - ReadAction.run(() -> { - if (!samType.isValid()) return; - - 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); - } - }); + private static MultiMap collectQueryKeys(SamDescriptor descriptor, Set candidateModules) { + MultiMap queries = MultiMap.createSet(); + queries.putValue(Location.UNKNOWN, descriptor.useScope); + queries.putValue(new FunctionalExpressionKey.TypedLocation(assertNotNull(descriptor.samClass.getName())), descriptor.useScope); //collect all methods with parameter of functional interface or free type parameter type - for (final PsiMethod psiMethod : getCandidateMethodsWithSuitableParams(aClass, useScope, candidateModules, samParamCount, samType)) { + for (final PsiMethod psiMethod : getCandidateMethodsWithSuitableParams(descriptor, candidateModules)) { ReadAction.run(() -> { - if (!psiMethod.isValid() || !samType.isValid()) return; + if (!psiMethod.isValid()) return; final GlobalSearchScope methodUseScope = convertToGlobalScope(psiMethod.getProject(), psiMethod.getUseScope()); - for (FunctionalExpressionKey key : getQueryKeys(aClass, samParamCount, samType, psiMethod)) { - queries.putValue(key, methodUseScope); + for (Location location : getPossibleCallLocations(descriptor.samClass, psiMethod)) { + queries.putValue(location, methodUseScope); } }); } @@ -191,11 +173,8 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase getQueryKeys(PsiClass samClass, - int samParamCount, - PsiType samType, - PsiMethod calledMethod) { - Set keys = new HashSet<>(); + private static Set getPossibleCallLocations(PsiClass samClass, PsiMethod calledMethod) { + Set keys = new HashSet<>(); String methodName = calledMethod.getName(); PsiParameter[] parameters = calledMethod.getParameterList().getParameters(); @@ -204,7 +183,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase generateKeys(int samMethodParamsCount, - PsiType samType, - FunctionalExpressionKey.Location location) { - List result = new ArrayList<>(); - for (int lambdaParamCount : new int[]{FunctionalExpressionKey.UNKNOWN_PARAM_COUNT, samMethodParamsCount}) { - 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; - } - private static int[] getPossibleArgCounts(PsiParameter[] parameters, int paramIndex) { if (parameters[parameters.length - 1].isVarArgs()) { return IntStream @@ -268,20 +229,16 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase getCandidateMethodsWithSuitableParams(final PsiClass samClass, - final GlobalSearchScope useScope, - final Set candidateModules, - int expectedFunExprParamsCount, - PsiType samType) { + private static Collection getCandidateMethodsWithSuitableParams(SamDescriptor descriptor, Set candidateModules) { return ApplicationManager.getApplication().runReadAction(new Computable>() { @Override public Collection compute() { - if (!samClass.isValid()) return Collections.emptyList(); + if (!descriptor.samClass.isValid()) return Collections.emptyList(); - GlobalSearchScope visibleFromCandidates = combineResolveScopes(candidateModules, samClass); + GlobalSearchScope visibleFromCandidates = combineResolveScopes(candidateModules, descriptor.samClass); if (visibleFromCandidates == null) return Collections.emptyList(); - Set usedMethodNames = collectMethodNamesCalledWithFunExpressions(samType, expectedFunExprParamsCount, useScope); + Set usedMethodNames = collectMethodNamesCalledWithFunExpressions(descriptor); Set methods = ContainerUtil.newLinkedHashSet(); Processor methodProcessor = method -> { @@ -293,8 +250,8 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase key = JavaMethodParameterTypesIndex.getInstance().getKey(); StubIndex index = StubIndex.getInstance(); - Project project = samClass.getProject(); - index.processElements(key, assertNotNull(samClass.getName()), project, useScope.intersectWith(visibleFromCandidates), PsiMethod.class, methodProcessor); + Project project = descriptor.samClass.getProject(); + index.processElements(key, assertNotNull(descriptor.samClass.getName()), project, descriptor.useScope.intersectWith(visibleFromCandidates), PsiMethod.class, methodProcessor); index.processElements(key, JavaMethodElementType.TYPE_PARAMETER_PSEUDO_NAME, project, visibleFromCandidates, PsiMethod.class, methodProcessor); LOG.info("#methods: " + methods.size()); return methods; @@ -303,18 +260,16 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase collectMethodNamesCalledWithFunExpressions(PsiType samType, int samParamCount, GlobalSearchScope useScope) { - boolean booleanCompatible = FunctionalExpressionKey.isBooleanCompatible(samType); - boolean isVoid = PsiType.VOID.equals(samType); - + private static Set collectMethodNamesCalledWithFunExpressions(SamDescriptor descriptor) { Set usedMethodNames = new HashSet<>(); StubIndex.getInstance().processAllKeys(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, key -> { ProgressManager.checkCanceled(); - if (key.canRepresent(samParamCount, booleanCompatible, isVoid) && key.location instanceof CallLocation) { + if (key.canRepresent(descriptor.samParamCount, descriptor.booleanCompatible, descriptor.isVoid) && + key.location instanceof CallLocation) { usedMethodNames.add(((CallLocation)key.location).methodName); } return true; - }, useScope, null); + }, descriptor.useScope, null); return usedMethodNames; } @@ -378,4 +333,36 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase !m.hasModifierProperty(PsiModifier.DEFAULT)).count() == 0; } + + private static class SamDescriptor { + final PsiClass samClass; + final GlobalSearchScope useScope; + final int samParamCount; + final boolean booleanCompatible; + final boolean isVoid; + + SamDescriptor(PsiClass samClass, PsiMethod samMethod, PsiType samType, GlobalSearchScope useScope) { + this.samClass = samClass; + this.useScope = useScope; + this.samParamCount = samMethod.getParameterList().getParametersCount(); + this.booleanCompatible = FunctionalExpressionKey.isBooleanCompatible(samType); + this.isVoid = PsiType.VOID.equals(samType); + } + + List generateKeys(Location location) { + List 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; + } + } } \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/PsiBinaryExpression.java b/java/java-psi-api/src/com/intellij/psi/PsiBinaryExpression.java index 107c02f87b67..f7c8bb2954ec 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiBinaryExpression.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiBinaryExpression.java @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import com.intellij.psi.tree.IElementType; @@ -24,6 +25,10 @@ import com.intellij.psi.tree.IElementType; * N.B Please use {@link PsiPolyadicExpression} instead as a more general form of an infix operator-expression. */ public interface PsiBinaryExpression extends PsiPolyadicExpression { + TokenSet BOOLEAN_OPERATION_TOKENS = TokenSet.create(JavaTokenType.EQEQ, JavaTokenType.NE, + JavaTokenType.LT, JavaTokenType.GT, JavaTokenType.LE, JavaTokenType.GE, + JavaTokenType.OROR, JavaTokenType.ANDAND); + /** * Returns the left operand of the expression. * diff --git a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java index cf8761cd1758..31e8027ad2e2 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java @@ -1464,14 +1464,7 @@ public class TypeConversionUtil { if (lType instanceof PsiClassType) lType = PsiPrimitiveType.getUnboxedType(lType); return lType; } - if (sign == JavaTokenType.EQEQ || - sign == JavaTokenType.NE || - sign == JavaTokenType.LT || - sign == JavaTokenType.GT || - sign == JavaTokenType.LE || - sign == JavaTokenType.GE || - sign == JavaTokenType.OROR || - sign == JavaTokenType.ANDAND) { + if (PsiBinaryExpression.BOOLEAN_OPERATION_TOKENS.contains(sign)) { return PsiType.BOOLEAN; } if (sign == JavaTokenType.OR || sign == JavaTokenType.XOR || sign == JavaTokenType.AND) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/FunctionalExpressionElementType.java b/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/FunctionalExpressionElementType.java index ed77e343b2b6..e34021ddafe0 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/FunctionalExpressionElementType.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/java/stubs/FunctionalExpressionElementType.java @@ -19,6 +19,7 @@ import com.intellij.lang.LighterAST; import com.intellij.lang.LighterASTNode; import com.intellij.openapi.util.Ref; import com.intellij.psi.JavaTokenType; +import com.intellij.psi.PsiBinaryExpression; import com.intellij.psi.PsiFunctionalExpression; import com.intellij.psi.impl.cache.RecordUtil; import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey.CoarseType; @@ -78,18 +79,24 @@ public abstract class FunctionalExpressionElementType returnsSomething = Ref.create(null); final AtomicBoolean isBoolean = new AtomicBoolean(); @@ -124,20 +134,20 @@ public abstract class FunctionalExpressionElementType children = LightTreeUtil.getChildrenOfType(tree, expr, ElementType.EXPRESSION_BIT_SET); + return children.size() == 3 && (isBooleanExpression(tree, children.get(1)) || isBooleanExpression(tree, children.get(2))); } return false; } @@ -208,7 +238,7 @@ public abstract class FunctionalExpressionElementType { - public static final int STUB_VERSION = 32; + public static final int STUB_VERSION = 33; public JavaFileElementType() { super("java.FILE", JavaLanguage.INSTANCE); diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/LightTreeUtil.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/LightTreeUtil.java index 573eb7f06767..5b3e2144ad3a 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/LightTreeUtil.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/LightTreeUtil.java @@ -147,4 +147,19 @@ public class LightTreeUtil { } tree.disposeChildren(children); } + + @Nullable + public static LighterASTNode getParentOfType(@NotNull LighterAST tree, @Nullable LighterASTNode node, + @NotNull TokenSet types, @NotNull TokenSet stopAt) { + if (node == null) return null; + node = tree.getParent(node); + while (node != null) { + final IElementType type = node.getTokenType(); + if (types.contains(type)) return node; + if (stopAt.contains(type)) return null; + node = tree.getParent(node); + } + return null; + } + }