[java-inspections] ComparatorCombinatorsInspection: add explicit lambda parameter type when necessary

Fixes IDEA-279693 Replace with Comparator.comparing leads to red code
Also: improve RedundantLambdaParameterTypeInspection via isSafeLambdaReplacement

GitOrigin-RevId: 8125436ff758fe3e4a770b33933a94e44be199fb
This commit is contained in:
Tagir Valeev
2021-10-09 13:28:55 +00:00
committed by intellij-monorepo-bot
parent bcb843dd8f
commit ed28503329
7 changed files with 83 additions and 35 deletions
@@ -1,6 +1,7 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;
import com.intellij.codeInspection.lambda.RedundantLambdaParameterTypeInspection;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
@@ -21,10 +22,7 @@ import com.siyeh.ig.psiutils.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.*;
import static com.intellij.util.ObjectUtils.tryCast;
@@ -347,7 +345,8 @@ public class ComparatorCombinatorsInspection extends AbstractBaseJavaLocalInspec
else {
String parameterName = leftVar.getName();
PsiTypeElement typeElement = leftVar.getTypeElement();
String parameterDeclaration = typeElement == null ? parameterName : "(" + typeElement.getText() + " " + parameterName + ")";
String typeText = typeElement == null ? leftVar.getType().getCanonicalText() : typeElement.getText();
String parameterDeclaration = "(" + typeText + " " + parameterName + ")";
text = "java.util.Comparator." + methodName + "(" +
(parameterDeclaration + " -> " + left.getText()) + ")";
}
@@ -540,6 +539,7 @@ public class ComparatorCombinatorsInspection extends AbstractBaseJavaLocalInspec
PsiLambdaExpression lambda = (PsiLambdaExpression)element;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
if (parameters.length != 2) return;
boolean keepParameterTypes = parameters[0].getTypeElement() != null;
if (lambda.getBody() instanceof PsiCodeBlock) {
PsiStatement[] statements = ((PsiCodeBlock)lambda.getBody()).getStatements();
if(statements.length > 1) {
@@ -559,11 +559,11 @@ public class ComparatorCombinatorsInspection extends AbstractBaseJavaLocalInspec
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
PsiExpression replacement = factory.createExpressionFromText(text, element);
PsiMethodCallExpression result = (PsiMethodCallExpression)lambda.replace(replacement);
normalizeLambda(ArrayUtil.getFirstElement(result.getArgumentList().getExpressions()), factory);
normalizeLambda(ArrayUtil.getFirstElement(result.getArgumentList().getExpressions()), factory, keepParameterTypes);
CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result));
}
private static void normalizeLambda(PsiExpression expression, PsiElementFactory factory) {
private static void normalizeLambda(PsiExpression expression, PsiElementFactory factory, boolean keepParameterTypes) {
if (!(expression instanceof PsiLambdaExpression)) return;
PsiLambdaExpression lambda = (PsiLambdaExpression)expression;
PsiParameter[] parameters = lambda.getParameterList().getParameters();
@@ -578,6 +578,9 @@ public class ComparatorCombinatorsInspection extends AbstractBaseJavaLocalInspec
.nonNull().forEach(nameElement -> nameElement.replace(factory.createIdentifier(name)));
parameter.setName(name);
}
if (!keepParameterTypes) {
RedundantLambdaParameterTypeInspection.removeLambdaParameterTypesIfPossible(lambda);
}
}
}
}
@@ -8,12 +8,13 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
public class RedundantLambdaParameterTypeInspection extends AbstractBaseJavaLocalInspectionTool {
public static final Logger LOG = Logger.getInstance(RedundantLambdaParameterTypeInspection.class);
@@ -48,36 +49,34 @@ public class RedundantLambdaParameterTypeInspection extends AbstractBaseJavaLoca
}
if (parameters.length == 0) return false;
final PsiType functionalInterfaceType = expression.getFunctionalInterfaceType();
if (functionalInterfaceType != null) {
final PsiElement lambdaParent = expression.getParent();
if (lambdaParent instanceof PsiExpressionList) {
final PsiElement gParent = lambdaParent.getParent();
if (gParent instanceof PsiCallExpression && ((PsiCallExpression)gParent).getTypeArguments().length == 0) {
final JavaResolveResult resolveResult = ((PsiCallExpression)gParent).resolveMethodGenerics();
final PsiMethod method = (PsiMethod)resolveResult.getElement();
if (method == null) return false;
final int idx = LambdaUtil.getLambdaIdx((PsiExpressionList)lambdaParent, expression);
if (idx < 0) return false;
PsiCallExpression copy = (PsiCallExpression)gParent.copy();
PsiLambdaExpression lambdaToStripTypeParameters = (PsiLambdaExpression)copy.getArgumentList().getExpressions()[idx];
for (PsiParameter parameter : lambdaToStripTypeParameters.getParameterList().getParameters()) {
parameter.getTypeElement().delete();
}
return functionalInterfaceType.equals(lambdaToStripTypeParameters.getFunctionalInterfaceType());
}
if (functionalInterfaceType == null) return false;
return LambdaUtil.isSafeLambdaReplacement(expression, () -> {
PsiLambdaExpression lambdaWithoutParameters = (PsiLambdaExpression)expression.copy();
for (PsiParameter parameter : lambdaWithoutParameters.getParameterList().getParameters()) {
PsiTypeElement typeElement = Objects.requireNonNull(parameter.getTypeElement());
typeElement.delete();
}
return true;
return lambdaWithoutParameters;
});
}
/**
* Removes lambda parameter types when possible
*
* @param lambdaExpression lambda expression to process
*/
public static void removeLambdaParameterTypesIfPossible(@NotNull PsiLambdaExpression lambdaExpression) {
PsiParameterList list = lambdaExpression.getParameterList();
if (isApplicable(list)) {
removeTypes(lambdaExpression);
}
return false;
}
private static void removeTypes(PsiLambdaExpression lambdaExpression) {
if (lambdaExpression != null) {
final PsiParameter[] parameters = lambdaExpression.getParameterList().getParameters();
if (PsiUtil.isLanguageLevel11OrHigher(lambdaExpression) &&
Arrays.stream(parameters).anyMatch(parameter -> keepVarType(parameter))) {
ContainerUtil.exists(parameters, parameter -> keepVarType(parameter))) {
for (PsiParameter parameter : parameters) {
PsiTypeElement element = parameter.getTypeElement();
if (element != null) {
@@ -101,8 +100,7 @@ public class RedundantLambdaParameterTypeInspection extends AbstractBaseJavaLoca
}
private static boolean keepVarType(PsiParameter parameter) {
return parameter.hasModifierProperty(PsiModifier.FINAL) ||
parameter.getAnnotations().length > 0;
return parameter.hasModifierProperty(PsiModifier.FINAL) || parameter.getAnnotations().length > 0;
}
private static class LambdaParametersFix implements LocalQuickFix {
@@ -0,0 +1,15 @@
// "Replace with 'Comparator.comparing'" "true"
import java.util.Comparator;
class CodeSample {
public void foo() {
final Comparator CMP = (Comparator<Entity>) Comparator.comparing((Entity o) -> o.getUuid());
}
private class Entity {
public <T> Comparable getUuid() {
return null;
}
}
}
@@ -0,0 +1,16 @@
// "Replace with 'Comparator.comparing'" "true"
import java.util.Comparator;
class CodeSample {
public void foo() {
final Comparator CMP = (Comparator<Entity>) (o1,<caret> o2) -> o1.getUuid()
.compareTo(o2.getUuid());
}
private class Entity {
public <T> Comparable getUuid() {
return null;
}
}
}
@@ -1,4 +1,4 @@
// "Remove redundant types" "false"
// "Remove redundant types" "true"
class Test2 {
class Y<T>{
T t;
@@ -0,0 +1,16 @@
// "Remove redundant types" "true"
class Test2 {
class Y<T>{
T t;
}
interface I<X> {
X foo(Y<X> list);
}
static <T> I<T> bar(I<T> i){return i;}
{
Test2.bar(y<caret> -> y.t);
}
}
@@ -61,7 +61,7 @@ public class RedundantLambdaParameterTypeInspectionTest extends LightJavaCodeIns
}
public void testCallNoTypeArgs() {
assertIntentionNotAvailable();
doTest();
}
public void testCallNoTypeArgs1() {