diff --git a/java/java-impl/src/com/intellij/codeInspection/RedundantLambdaParameterTypeInspection.java b/java/java-impl/src/com/intellij/codeInspection/RedundantLambdaParameterTypeInspection.java new file mode 100644 index 000000000000..d383eeab458b --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/RedundantLambdaParameterTypeInspection.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2012 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.codeInspection; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInsight.intention.HighPriorityAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.Function; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +/** + * User: anna + */ +public class RedundantLambdaParameterTypeInspection extends BaseJavaLocalInspectionTool { + public static final Logger LOG = Logger.getInstance("#" + RedundantLambdaParameterTypeInspection.class.getName()); + + @Nls + @NotNull + @Override + public String getGroupDisplayName() { + return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "Redundant lambda parameter type"; + } + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @NotNull + @Override + public String getShortName() { + return "RedundantLambdaParameterType"; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + super.visitLambdaExpression(expression); + final PsiParameter[] parameters = expression.getParameterList().getParameters(); + for (PsiParameter parameter : parameters) { + if (parameter.getTypeElement() == null) return; + } + if (parameters.length == 0) return; + final PsiType functionalInterfaceType = LambdaUtil.getFunctionalInterfaceType(expression, false); + if (functionalInterfaceType != null) { + if (!LambdaUtil.isLambdaFullyInferred(expression, functionalInterfaceType)) { + final PsiElement parent = expression.getParent(); + if (parent instanceof PsiExpressionList) { + final PsiElement gParent = parent.getParent(); + if (gParent instanceof PsiCallExpression && ((PsiCallExpression)gParent).getTypeArguments().length == 0) { + final PsiMethod method = ((PsiCallExpression)gParent).resolveMethod(); + if (method == null) return; + final int idx = LambdaUtil.getLambdaIdx((PsiExpressionList)parent, expression); + if (idx < 0) return; + + final PsiTypeParameter[] typeParameters = method.getTypeParameters(); + final PsiExpression[] arguments = ((PsiExpressionList)parent).getExpressions(); + final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(holder.getProject()); + arguments[idx] = javaPsiFacade.getElementFactory().createExpressionFromText("null", expression); + final PsiSubstitutor substitutor = javaPsiFacade.getResolveHelper() + .inferTypeArguments(typeParameters, method.getParameterList().getParameters(), arguments, PsiSubstitutor.EMPTY, + gParent, DefaultParameterTypeInferencePolicy.INSTANCE); + + for (PsiTypeParameter parameter : typeParameters) { + final PsiType psiType = substitutor.substitute(parameter); + if (psiType == null || LambdaUtil.dependsOnTypeParams(psiType, expression)) return; + } + } + } + } + holder.registerProblem(expression.getParameterList(), "Redundant parameter type declarations", + ProblemHighlightType.LIKE_UNUSED_SYMBOL, new RemoveTypeDeclarationsFix()); + } + } + }; + } + + private static class RemoveTypeDeclarationsFix implements LocalQuickFix, HighPriorityAction { + + @NotNull + @Override + public String getName() { + return "Remove redundant types"; + } + + @NotNull + @Override + public String getFamilyName() { + return getName(); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + if (element != null) { + final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(element, PsiLambdaExpression.class); + removeTypes(lambdaExpression); + } + } + + private static void removeTypes(PsiLambdaExpression lambdaExpression) { + if (lambdaExpression != null) { + final PsiParameter[] parameters = lambdaExpression.getParameterList().getParameters(); + final String text; + if (parameters.length == 1) { + text = parameters[0].getName(); + } + else { + text = "(" + StringUtil.join(parameters, new Function() { + @Override + public String fun(PsiParameter parameter) { + return parameter.getName(); + } + }, ", ") + ")"; + } + final PsiLambdaExpression expression = (PsiLambdaExpression)JavaPsiFacade.getElementFactory(lambdaExpression.getProject()) + .createExpressionFromText(text + "->{}", lambdaExpression); + lambdaExpression.getParameterList().replace(expression.getParameterList()); + } + } + } +} diff --git a/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java b/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java index 516bd254fdc4..fb8cc3967f60 100644 --- a/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/LambdaUtil.java @@ -17,11 +17,8 @@ package com.intellij.psi; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.RecursionGuard; -import com.intellij.openapi.util.RecursionManager; import com.intellij.psi.infos.MethodCandidateInfo; import com.intellij.psi.util.*; -import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -300,48 +297,47 @@ public class LambdaUtil { while (parent instanceof PsiParenthesizedExpression) { parent = parent.getParent(); } - PsiType type = null; if (parent instanceof PsiTypeCastExpression) { - type = ((PsiTypeCastExpression)parent).getType(); + return ((PsiTypeCastExpression)parent).getType(); } else if (parent instanceof PsiVariable) { - type = ((PsiVariable)parent).getType(); + return ((PsiVariable)parent).getType(); } else if (parent instanceof PsiAssignmentExpression) { final PsiExpression lExpression = ((PsiAssignmentExpression)parent).getLExpression(); - type = lExpression.getType(); + return lExpression.getType(); } else if (parent instanceof PsiExpressionList) { final PsiExpressionList expressionList = (PsiExpressionList)parent; - int lambdaIdx = getLambdaIdx(expressionList, expression); + final int lambdaIdx = getLambdaIdx(expressionList, expression); if (lambdaIdx > -1) { - if (tryToSubstitute) { - final PsiElement gParent = expressionList.getParent(); - if (gParent instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression contextCall = (PsiMethodCallExpression)gParent; - final JavaResolveResult resolveResult = contextCall.resolveMethodGenerics(); - final PsiElement resolve = resolveResult.getElement(); - if (resolve instanceof PsiMethod) { - final PsiParameter[] parameters = ((PsiMethod)resolve).getParameterList().getParameters(); - if (lambdaIdx < parameters.length) { - type = parameters[lambdaIdx].getType(); - final PsiType psiType = type; - type = PsiResolveHelper.ourGuard.doPreventingRecursion(expression, true, new Computable() { - @Override - public PsiType compute() { - return resolveResult.getSubstitutor().substitute(psiType); - } - }); - } - } - } - } else { - final Map currentMethodCandidates = MethodCandidateInfo.CURRENT_CANDIDATE.get(); + + if (!tryToSubstitute) { + final Map currentMethodCandidates = MethodCandidateInfo.CURRENT_CANDIDATE.get(); final PsiMethod method = currentMethodCandidates != null ? currentMethodCandidates.get(parent) : null; if (method != null) { final PsiParameter[] parameters = method.getParameterList().getParameters(); + return lambdaIdx < parameters.length ? parameters[lambdaIdx].getType() : null; + } + } + + final PsiElement gParent = expressionList.getParent(); + if (gParent instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression contextCall = (PsiMethodCallExpression)gParent; + final JavaResolveResult resolveResult = contextCall.resolveMethodGenerics(); + final PsiElement resolve = resolveResult.getElement(); + if (resolve instanceof PsiMethod) { + final PsiParameter[] parameters = ((PsiMethod)resolve).getParameterList().getParameters(); if (lambdaIdx < parameters.length) { - type = parameters[lambdaIdx].getType(); + if (!tryToSubstitute) { + return parameters[lambdaIdx].getType(); + } + return PsiResolveHelper.ourGuard.doPreventingRecursion(expression, true, new Computable() { + @Override + public PsiType compute() { + return resolveResult.getSubstitutor().substitute(parameters[lambdaIdx].getType()); + } + }); } } } @@ -350,16 +346,16 @@ public class LambdaUtil { else if (parent instanceof PsiReturnStatement) { final PsiMethod method = PsiTreeUtil.getParentOfType(parent, PsiMethod.class); if (method != null) { - type = method.getReturnType(); + return method.getReturnType(); } } else if (parent instanceof PsiLambdaExpression) { final PsiType parentInterfaceType = ((PsiLambdaExpression)parent).getFunctionalInterfaceType(); if (parentInterfaceType != null) { - type = getFunctionalInterfaceReturnType(parentInterfaceType); + return getFunctionalInterfaceReturnType(parentInterfaceType); } } - return type; + return null; } public static PsiType getLambdaParameterType(PsiParameter param) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/PsiResolveHelperImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/PsiResolveHelperImpl.java index 1800505685c0..e3b8ba6e3a21 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/PsiResolveHelperImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/PsiResolveHelperImpl.java @@ -652,6 +652,7 @@ public class PsiResolveHelperImpl implements PsiResolveHelper { PsiClassType.ClassResolveResult resolveResult, PsiMethod method, PsiLambdaExpression lambdaExpression) { final PsiParameter[] parameters = lambdaExpression.getParameterList().getParameters(); + if (parameters.length == 0) return null; final PsiType[] lambdaArgs = new PsiType[parameters.length]; for (int i = 0; i < parameters.length; i++) { PsiParameter parameter = parameters[i]; diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterAssignment.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterAssignment.java new file mode 100644 index 000000000000..91ad98052b5f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterAssignment.java @@ -0,0 +1,6 @@ +// "Remove redundant types" "true" +class Test { + { + Comparable r = o-> 1; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterCallWithTypeArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterCallWithTypeArgs.java new file mode 100644 index 000000000000..872ebab8624c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterCallWithTypeArgs.java @@ -0,0 +1,16 @@ +// "Remove redundant types" "true" +class Test2 { + class Y{ + T t; + } + + interface I { + X foo(Y list); + } + + static I bar(I i){return i;} + + { + Test2.bar(y-> y.t); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterInferredFromOtherArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterInferredFromOtherArgs.java new file mode 100644 index 000000000000..7e5d950f7665 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/afterInferredFromOtherArgs.java @@ -0,0 +1,15 @@ +// "Remove redundant types" "true" +class ReturnTypeCompatibility { + + interface I1 { + L m(L x); + } + + static

void call(P p, I1

i2) { + i2.m(null); + } + + public static void main(String[] args) { + call("", i-> ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignment.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignment.java new file mode 100644 index 000000000000..35f16c7c795d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignment.java @@ -0,0 +1,6 @@ +// "Remove redundant types" "true" +class Test { + { + Comparable r = (String o) -> 1; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignmentNoParams.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignmentNoParams.java new file mode 100644 index 000000000000..5aa85e4f4b08 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignmentNoParams.java @@ -0,0 +1,6 @@ +// "Remove redundant types" "false" +class Test { + { + Runnable r = () -> {}; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignmentNoTypes.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignmentNoTypes.java new file mode 100644 index 000000000000..ebe9f29ed063 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeAssignmentNoTypes.java @@ -0,0 +1,6 @@ +// "Remove redundant types" "false" +class Test { + { + Comparable r = o -> 1; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallNoTypeArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallNoTypeArgs.java new file mode 100644 index 000000000000..094dbbe61308 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallNoTypeArgs.java @@ -0,0 +1,16 @@ +// "Remove redundant types" "false" +class Test2 { + class Y{ + T t; + } + + interface I { + X foo(Y list); + } + + static I bar(I i){return i;} + + { + Test2.bar((Yring> y) -> y.t); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallNoTypeArgs1.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallNoTypeArgs1.java new file mode 100644 index 000000000000..64eb6e2861ad --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallNoTypeArgs1.java @@ -0,0 +1,13 @@ +// "Remove redundant types" "false" +class NoInferenceResult { + interface I { + B foo(A a); + } + + I m(I f) { return null; } + + void test() { + m((String s1) -> s1.length()); + m((String s1) -> s1); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallWithTypeArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallWithTypeArgs.java new file mode 100644 index 000000000000..ca48d5db97f9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeCallWithTypeArgs.java @@ -0,0 +1,16 @@ +// "Remove redundant types" "true" +class Test2 { + class Y{ + T t; + } + + interface I { + X foo(Y list); + } + + static I bar(I i){return i;} + + { + Test2.bar((Yring> y) -> y.t); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeInferredFromOtherArgs.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeInferredFromOtherArgs.java new file mode 100644 index 000000000000..abf7228a0904 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType/beforeInferredFromOtherArgs.java @@ -0,0 +1,15 @@ +// "Remove redundant types" "true" +class ReturnTypeCompatibility { + + interface I1 { + L m(L x); + } + + static

void call(P p, I1

i2) { + i2.m(null); + } + + public static void main(String[] args) { + call("", (String i) -> ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/RedundantLambdaParameterTypeInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/RedundantLambdaParameterTypeInspectionTest.java new file mode 100644 index 000000000000..6842416f7533 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/RedundantLambdaParameterTypeInspectionTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2012 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.codeInsight.daemon.quickFix; + +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.RedundantLambdaParameterTypeInspection; + + +public class RedundantLambdaParameterTypeInspectionTest extends LightQuickFixTestCase { + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{ + new RedundantLambdaParameterTypeInspection(), + }; + } + + public void test() throws Exception { doAllTests(); } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/redundantLambdaParameterType"; + } + +} \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/RedundantLambdaParameterType.html b/resources-en/src/inspectionDescriptions/RedundantLambdaParameterType.html new file mode 100644 index 000000000000..8b4d1426e49f --- /dev/null +++ b/resources-en/src/inspectionDescriptions/RedundantLambdaParameterType.html @@ -0,0 +1,5 @@ + + +This inspection reports lambda expressions with formal parameter types which can be inferred from context and thus are redundant. + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index a69c5818bd71..27aa254047aa 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -536,6 +536,9 @@ +