From f9d49fa1f470151f403a6ab98c5990c466009b29 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 22 Feb 2016 15:07:01 +0100 Subject: [PATCH] convert to diamonds: take care of java 8 inference, check on non-physical elements (IDEA-151559) --- .../RedundantTypeArgsInspection.java | 114 ++++----------- .../intellij/psi/impl/PsiDiamondTypeUtil.java | 136 +++++++++++++++--- .../afterCantCollapseDiamonds.java | 2 +- .../quickFix/explicit2diamond/after4.java | 16 +++ .../quickFix/explicit2diamond/before4.java | 2 +- .../quickFix/explicit2diamond/before6.java | 2 +- .../beforeOverloadContainingMethods.java | 14 ++ .../Simplify2DiamondInspectionsTest.java | 10 +- 8 files changed, 188 insertions(+), 108 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/after4.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/beforeOverloadContainingMethods.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/miscGenerics/RedundantTypeArgsInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/miscGenerics/RedundantTypeArgsInspection.java index 01ca5847c213..64f2d47059ab 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/miscGenerics/RedundantTypeArgsInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/miscGenerics/RedundantTypeArgsInspection.java @@ -21,9 +21,8 @@ import com.intellij.codeInspection.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.psi.*; +import com.intellij.psi.impl.PsiDiamondTypeUtil; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiTypesUtil; -import com.intellij.psi.util.PsiUtil; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; @@ -36,11 +35,7 @@ import java.util.List; public class RedundantTypeArgsInspection extends GenericsInspectionToolBase { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.miscGenerics.RedundantTypeArgsInspection"); - public RedundantTypeArgsInspection() { - myQuickFixAction = new MyQuickFixAction(); - } - - private final LocalQuickFix myQuickFixAction; + private final static LocalQuickFix ourQuickFixAction = new MyQuickFixAction(); @Override @NotNull @@ -101,91 +96,38 @@ public class RedundantTypeArgsInspection extends GenericsInspectionToolBase { super.visitMethodReferenceExpression(expression); checkMethodReference(expression, inspectionManager, problems); } - - private void checkCallExpression(final PsiJavaCodeReferenceElement reference, - final PsiType[] typeArguments, - PsiCallExpression expression, - final InspectionManager inspectionManager, final List problems) { - - PsiExpressionList argumentList = expression.getArgumentList(); - if (argumentList == null) return; - final JavaResolveResult resolveResult = reference.advancedResolve(false); - - final PsiElement element = resolveResult.getElement(); - if (element instanceof PsiMethod && resolveResult.isValidResult()) { - PsiMethod method = (PsiMethod)element; - final PsiTypeParameter[] typeParameters = method.getTypeParameters(); - if (typeParameters.length == typeArguments.length) { - final PsiType typeByParent = PsiTypesUtil.getExpectedTypeByParent(expression); - if (typeByParent != null) { - final String arrayInitializer = "new " + typeByParent.getCanonicalText() + "[]{0}"; - final PsiNewExpression newExpr = - (PsiNewExpression)JavaPsiFacade.getInstance(expression.getProject()).getElementFactory().createExpressionFromText(arrayInitializer, expression); - final PsiArrayInitializerExpression initializer = newExpr.getArrayInitializer(); - LOG.assertTrue(initializer != null); - final PsiCallExpression copy = (PsiCallExpression)initializer.getInitializers()[0].replace(expression); - if (!isInferenceEquivalent(typeArguments, method, typeParameters, copy)) { - return; - } - } - else { - final int offset = expression.getTextRange().getStartOffset(); - final PsiFile containingFile = expression.getContainingFile(); - final PsiFile fileCopy = (PsiFile)containingFile.copy(); - final PsiElement elementInCopy = fileCopy.findElementAt(offset); - if (method.getContainingFile() == containingFile) { - method = PsiTreeUtil.getParentOfType(fileCopy.findElementAt(method.getTextOffset()), PsiMethod.class); - } - if (!isInferenceEquivalent(typeArguments, method, typeParameters, elementInCopy)) { - return; - } - } - final ProblemDescriptor descriptor = inspectionManager.createProblemDescriptor(expression.getTypeArgumentList(), - InspectionsBundle.message("inspection.redundant.type.problem.descriptor"), - myQuickFixAction, - ProblemHighlightType.LIKE_UNUSED_SYMBOL, false); - problems.add(descriptor); - } - } - } - - private boolean isInferenceEquivalent(PsiType[] typeArguments, - PsiMethod method, - PsiTypeParameter[] typeParameters, - PsiElement elementInCopy) { - final PsiCallExpression exprCopy = PsiTreeUtil.getParentOfType(elementInCopy, PsiCallExpression.class, false); - if (exprCopy != null) { - try { - final PsiMethodCallExpression expr = (PsiMethodCallExpression) - JavaPsiFacade.getInstance(exprCopy.getProject()).getElementFactory().createExpressionFromText("foo()", null); - exprCopy.getTypeArgumentList().replace(expr.getTypeArgumentList()); - } - catch (IncorrectOperationException e) { - LOG.error(e); - return false; - } - final JavaResolveResult copyResult = exprCopy.resolveMethodGenerics(); - if (method != copyResult.getElement()) return false; - final PsiSubstitutor psiSubstitutor = copyResult.getSubstitutor(); - for (int i = 0, length = typeParameters.length; i < length; i++) { - PsiTypeParameter typeParameter = typeParameters[i]; - final PsiType inferredType = psiSubstitutor.getSubstitutionMap().get(typeParameter); - if (!typeArguments[i].equals(inferredType)) { - return false; - } - if (PsiUtil.resolveClassInType(method.getReturnType()) == typeParameter && PsiPrimitiveType.getUnboxedType(inferredType) != null) { - return false; - } - } - } - return true; - } }); if (problems.isEmpty()) return null; return problems.toArray(new ProblemDescriptor[problems.size()]); } + private static void checkCallExpression(final PsiJavaCodeReferenceElement reference, + final PsiType[] typeArguments, + PsiCallExpression expression, + final InspectionManager inspectionManager, + final List problems) { + PsiExpressionList argumentList = expression.getArgumentList(); + if (argumentList == null) return; + final JavaResolveResult resolveResult = reference.advancedResolve(false); + + final PsiElement element = resolveResult.getElement(); + if (element instanceof PsiMethod && resolveResult.isValidResult()) { + PsiMethod method = (PsiMethod)element; + final PsiTypeParameter[] typeParameters = method.getTypeParameters(); + if (typeParameters.length == typeArguments.length) { + if (PsiDiamondTypeUtil.areTypeArgumentsRedundant(typeArguments, expression, false, method, typeParameters)) { + final ProblemDescriptor descriptor = inspectionManager.createProblemDescriptor(expression.getTypeArgumentList(), + InspectionsBundle.message( + "inspection.redundant.type.problem.descriptor"), + ourQuickFixAction, + ProblemHighlightType.LIKE_UNUSED_SYMBOL, false); + problems.add(descriptor); + } + } + } + } + private static void checkMethodReference(PsiMethodReferenceExpression expression, InspectionManager inspectionManager, List problems) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiDiamondTypeUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiDiamondTypeUtil.java index c21c70078b26..c449aa665f99 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiDiamondTypeUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiDiamondTypeUtil.java @@ -22,8 +22,10 @@ import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.util.Function; +import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -71,23 +73,11 @@ public class PsiDiamondTypeUtil { typeArguments = parameterList.getTypeArguments(); } if (types.size() == typeArguments.length) { - for (int i = 0, typeArgumentsLength = typeArguments.length; i < typeArgumentsLength; i++) { - PsiType typeArgument = typeArguments[i]; - if (types.get(i) instanceof PsiWildcardType) { - final PsiWildcardType wildcardType = (PsiWildcardType)types.get(i); - final PsiType bound = wildcardType.getBound(); - if (bound != null) { - if (wildcardType.isExtends()) { - if (bound.isAssignableFrom(typeArgument)) continue; - } - else { - if (typeArgument.isAssignableFrom(bound)) continue; - } - } - } - if (!typeArgument.equals(types.get(i))) { - return false; - } + final PsiMethod method = expression.resolveMethod(); + final PsiElement resolve = classReference.resolve(); + if (resolve instanceof PsiClass) { + final PsiTypeParameter[] typeParameters = ((PsiClass)resolve).getTypeParameters(); + return areTypeArgumentsRedundant(typeArguments, expression, true, method, typeParameters); } } return true; @@ -183,4 +173,116 @@ public class PsiDiamondTypeUtil { } return null; } + + public static boolean areTypeArgumentsRedundant(PsiType[] typeArguments, + PsiCallExpression expression, + boolean constructorRef, + @Nullable PsiMethod method, + PsiTypeParameter[] typeParameters) { + final PsiElement copy; + final PsiType typeByParent = PsiTypesUtil.getExpectedTypeByParent(expression); + if (typeByParent != null) { + final String arrayInitializer = "new " + typeByParent.getCanonicalText() + "[]{0}"; + final PsiNewExpression newExpr = + (PsiNewExpression)JavaPsiFacade.getInstance(expression.getProject()).getElementFactory().createExpressionFromText(arrayInitializer, expression); + final PsiArrayInitializerExpression initializer = newExpr.getArrayInitializer(); + LOG.assertTrue(initializer != null); + copy = initializer.getInitializers()[0].replace(expression); + } + else { + final int offset = expression.getTextRange().getStartOffset(); + final PsiFile containingFile = expression.getContainingFile(); + final PsiFile fileCopy = (PsiFile)containingFile.copy(); + copy = fileCopy.findElementAt(offset); + if (method != null && method.getContainingFile() == containingFile) { + method = PsiTreeUtil.getParentOfType(fileCopy.findElementAt(method.getTextOffset()), PsiMethod.class); + } + } + final PsiCallExpression exprCopy = PsiTreeUtil.getParentOfType(copy, PsiCallExpression.class, false); + if (exprCopy != null) { + try { + final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(exprCopy.getProject()).getElementFactory(); + if (constructorRef) { + if (!(exprCopy instanceof PsiNewExpression) || !isInferenceEquivalent(typeArguments, elementFactory, (PsiNewExpression)exprCopy)) { + return false; + } + } + else { + LOG.assertTrue(method != null); + if (!isInferenceEquivalent(typeArguments, elementFactory, exprCopy, method, typeParameters)) { + return false; + } + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + return false; + } + } + return true; + } + + private static boolean isInferenceEquivalent(PsiType[] typeArguments, + PsiElementFactory elementFactory, + PsiCallExpression exprCopy, + PsiMethod method, + PsiTypeParameter[] typeParameters) throws IncorrectOperationException { + PsiReferenceParameterList list = ((PsiCallExpression)elementFactory.createExpressionFromText("foo()", null)).getTypeArgumentList(); + exprCopy.getTypeArgumentList().replace(list); + + final JavaResolveResult copyResult = exprCopy.resolveMethodGenerics(); + if (method != copyResult.getElement()) return false; + final PsiSubstitutor psiSubstitutor = copyResult.getSubstitutor(); + for (int i = 0, length = typeParameters.length; i < length; i++) { + PsiTypeParameter typeParameter = typeParameters[i]; + final PsiType inferredType = psiSubstitutor.getSubstitutionMap().get(typeParameter); + if (!typeArguments[i].equals(inferredType)) { + return false; + } + if (PsiUtil.resolveClassInType(method.getReturnType()) == typeParameter && PsiPrimitiveType.getUnboxedType(inferredType) != null) { + return false; + } + } + return true; + } + + private static boolean isInferenceEquivalent(PsiType[] typeArguments, + PsiElementFactory elementFactory, + PsiNewExpression exprCopy) throws IncorrectOperationException { + final PsiJavaCodeReferenceElement collapsedClassReference = ((PsiNewExpression)elementFactory.createExpressionFromText("new A<>()", null)).getClassOrAnonymousClassReference(); + LOG.assertTrue(collapsedClassReference != null); + final PsiReferenceParameterList diamondParameterList = collapsedClassReference.getParameterList(); + LOG.assertTrue(diamondParameterList != null); + + final PsiJavaCodeReferenceElement classReference = exprCopy.getClassOrAnonymousClassReference(); + LOG.assertTrue(classReference != null); + final PsiReferenceParameterList parameterList = classReference.getParameterList(); + LOG.assertTrue(parameterList != null); + parameterList.replace(diamondParameterList); + + final PsiType[] inferredArgs = classReference.getParameterList().getTypeArguments(); + if (typeArguments.length != inferredArgs.length) { + return false; + } + + for (int i = 0; i < typeArguments.length; i++) { + PsiType typeArgument = typeArguments[i]; + if (inferredArgs[i] instanceof PsiWildcardType) { + final PsiWildcardType wildcardType = (PsiWildcardType)inferredArgs[i]; + final PsiType bound = wildcardType.getBound(); + if (bound != null) { + if (wildcardType.isExtends()) { + if (bound.isAssignableFrom(typeArgument)) continue; + } + else { + if (typeArgument.isAssignableFrom(bound)) continue; + } + } + } + if (!typeArgument.equals(inferredArgs[i])) { + return false; + } + } + return true; + } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeNewOperatorType/afterCantCollapseDiamonds.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeNewOperatorType/afterCantCollapseDiamonds.java index 2a7bdecad862..953149d05051 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeNewOperatorType/afterCantCollapseDiamonds.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/changeNewOperatorType/afterCantCollapseDiamonds.java @@ -7,6 +7,6 @@ class Foo { class Constructors { public static void main(String[] args) { - Foo foo2 = new Foo(1); + Foo foo2 = new Foo<>(1); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/after4.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/after4.java new file mode 100644 index 000000000000..e82ff8b428f3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/after4.java @@ -0,0 +1,16 @@ +// "Replace with <>" "true" +class Test { + + void test() { + class Foo { + Foo() {} + Foo(X x) {} + } + Foo f1 = new Foo<>(1); + Foo f2 = new Foo(); + Foo f3 = new Foo(); + Foo f4 = new Foo(1) {}; + Foo f5 = new Foo() {}; + Foo f6 = new Foo() {}; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/before4.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/before4.java index 35cf27363787..9ea3e7d3532b 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/before4.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/before4.java @@ -1,4 +1,4 @@ -// "Replace with <>" "false" +// "Replace with <>" "true" class Test { void test() { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/before6.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/before6.java index 48666f4d3c4a..69f14cfd0111 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/before6.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/before6.java @@ -1,4 +1,4 @@ -// "Replace with <>" "true" +// "Replace with <>" "false" class Test { void test() { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/beforeOverloadContainingMethods.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/beforeOverloadContainingMethods.java new file mode 100644 index 000000000000..62fc1aaf8818 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/beforeOverloadContainingMethods.java @@ -0,0 +1,14 @@ +// "Replace with <>" "false" +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.IntToLongFunction; + +abstract class SimplePlanet { + void a(IntToLongFunction r, List l) {} + void a(Function f, List l) {} + + { + a(a -> a, new ArrayListing>()); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/Simplify2DiamondInspectionsTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/Simplify2DiamondInspectionsTest.java index ac74905ef4c9..eac9703e7ff0 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/Simplify2DiamondInspectionsTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/Simplify2DiamondInspectionsTest.java @@ -18,10 +18,11 @@ package com.intellij.codeInsight.daemon.quickFix; import com.intellij.codeInspection.ExplicitTypeCanBeDiamondInspection; import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.pom.java.LanguageLevel; -import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.testFramework.IdeaTestUtil; import org.jetbrains.annotations.NotNull; @@ -65,6 +66,11 @@ public class Simplify2DiamondInspectionsTest extends LightQuickFixParameterizedT @Override protected LanguageLevel getLanguageLevel() { - return LanguageLevel.JDK_1_7; + return LanguageLevel.JDK_1_8; + } + + @Override + protected Sdk getProjectJDK() { + return IdeaTestUtil.getMockJdk18(); } } \ No newline at end of file