diff --git a/java/java-analysis-impl/src/com/intellij/psi/util/RedundantCastUtil.java b/java/java-analysis-impl/src/com/intellij/psi/util/RedundantCastUtil.java index 26bba33f0be6..424e460468ee 100644 --- a/java/java-analysis-impl/src/com/intellij/psi/util/RedundantCastUtil.java +++ b/java/java-analysis-impl/src/com/intellij/psi/util/RedundantCastUtil.java @@ -8,26 +8,26 @@ import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Key; import com.intellij.psi.*; -import com.intellij.psi.impl.source.PsiImmediateClassType; +import com.intellij.psi.impl.RecaptureTypeMapper; import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil; import com.intellij.psi.infos.MethodCandidateInfo; import com.intellij.psi.tree.IElementType; import com.intellij.util.CommonProcessors; import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; -import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.bugs.NullArgumentToVariableArgMethodInspection; import com.siyeh.ig.psiutils.ExpectedTypeUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; public class RedundantCastUtil { private static final Logger LOG = Logger.getInstance(RedundantCastUtil.class); - private static final Key SELF_REFERENCE = Key.create("SELF_REFERENCE"); private RedundantCastUtil() { } @@ -269,9 +269,9 @@ public class RedundantCastUtil { if (newCall == null) return false; PsiExpression newQualifier = deparenthesizeExpression(newCall.getMethodExpression().getQualifierExpression()); LOG.assertTrue(newQualifier != null); - PsiElement oldReference = newQualifier.getCopyableUserData(SELF_REFERENCE); + PsiElement oldReference = newQualifier.getCopyableUserData(RecaptureTypeMapper.SELF_REFERENCE); PsiElement replace = newQualifier.replace(getInnerMostOperand(newQualifier)); - replace.putCopyableUserData(SELF_REFERENCE, oldReference); + replace.putCopyableUserData(RecaptureTypeMapper.SELF_REFERENCE, oldReference); final JavaResolveResult newResult = newCall.getMethodExpression().advancedResolve(false); if (!newResult.isValidResult()) return false; @@ -282,7 +282,7 @@ public class RedundantCastUtil { PsiType newReturnType = newCall.getType(); PsiType oldReturnType = ((PsiMethodCallExpression)parent).getType(); - if (Comparing.equal(newReturnType == null ? null : new RedundantCastRecaptureMapper().mapType(newReturnType), oldReturnType) && + if (Comparing.equal(newReturnType == null ? null : new RecaptureTypeMapper().mapType(newReturnType), oldReturnType) && (Comparing.equal(newTargetMethod, targetMethod) || !(newTargetMethod.isDeprecated() && !targetMethod.isDeprecated()) && MethodSignatureUtil.isSuperMethod(newTargetMethod, targetMethod) && @@ -391,7 +391,7 @@ public class RedundantCastUtil { return oldMethod.equals(newResult.getElement()) && newResult.isValidResult() && !(newResult instanceof MethodCandidateInfo && ((MethodCandidateInfo)newResult).getInferenceErrorMessage() != null) && - new RedundantCastRecaptureMapper().recapture(newResult.getSubstitutor()).equals(oldResult.getSubstitutor()); + new RecaptureTypeMapper().recapture(newResult.getSubstitutor()).equals(oldResult.getSubstitutor()); } private static boolean castForBoxing(PsiExpression operand, PsiType oppositeType, PsiType conditionalType) { @@ -557,43 +557,16 @@ public class RedundantCastUtil { @Nullable private static PsiCall copyCallExpression(PsiCall expression, PsiType typeByParent) { - class Encoder { - private void encode(PsiElement expression) { - expression.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitElement(@NotNull PsiElement element) { - if (element instanceof PsiExpression) { - element.putCopyableUserData(SELF_REFERENCE, element); - } - super.visitElement(element); - } - }); - } - - private void clean(PsiElement expression) { - expression.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitElement(@NotNull PsiElement element) { - if (element instanceof PsiExpression) { - element.putCopyableUserData(SELF_REFERENCE, null); - } - super.visitElement(element); - } - }); - } - } - - Encoder encoder = new Encoder(); PsiElement encoded = null; try { if (typeByParent != null && PsiTypesUtil.isDenotableType(typeByParent, expression)) { - encoder.encode(encoded = expression); + RecaptureTypeMapper.encode(encoded = expression); return (PsiCall)LambdaUtil.copyWithExpectedType(expression, typeByParent); } else { final PsiCall call = LambdaUtil.treeWalkUp(expression); if (call != null) { - encoder.encode(encoded = call); + RecaptureTypeMapper.encode(encoded = call); Object marker = new Object(); PsiTreeUtil.mark(expression, marker); final PsiCall callCopy = LambdaUtil.copyTopLevelCall(call); @@ -601,7 +574,7 @@ public class RedundantCastUtil { return (PsiCall)PsiTreeUtil.releaseMark(callCopy, marker); } else { - encoder.encode(encoded = expression); + RecaptureTypeMapper.encode(encoded = expression); return (PsiCall)expression.copy(); } } @@ -611,7 +584,7 @@ public class RedundantCastUtil { } finally { if (encoded != null) { - encoder.clean(encoded); + RecaptureTypeMapper.clean(encoded); } } } @@ -966,49 +939,6 @@ public class RedundantCastUtil { } super.visitArrayAccessExpression(expression); } - - private static class RedundantCastRecaptureMapper extends PsiTypeMapper { - private final Set myVisited = ContainerUtil.newIdentityTroveSet(); - - @Override - public PsiType visitType(@NotNull PsiType type) { - return type; - } - - @Override - public PsiType visitClassType(@NotNull PsiClassType classType) { - if (!myVisited.add(classType)) return classType; - final PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics(); - final PsiClass psiClass = classResolveResult.getElement(); - final PsiSubstitutor substitutor = classResolveResult.getSubstitutor(); - if (psiClass == null) return classType; - return new PsiImmediateClassType(psiClass, recapture(substitutor)); - } - - PsiSubstitutor recapture(PsiSubstitutor substitutor) { - PsiSubstitutor result = PsiSubstitutor.EMPTY; - for (Map.Entry entry : substitutor.getSubstitutionMap().entrySet()) { - PsiType value = entry.getValue(); - result = result.put(entry.getKey(), value == null ? null : mapType(value)); - } - return result; - } - - @Override - public PsiType visitCapturedWildcardType(@NotNull PsiCapturedWildcardType capturedWildcardType) { - PsiElement context = capturedWildcardType.getContext(); - @Nullable PsiElement original = context.getCopyableUserData(SELF_REFERENCE); - if (original != null) { - context = original; - } - PsiCapturedWildcardType mapped = - PsiCapturedWildcardType.create(capturedWildcardType.getWildcard(), context, capturedWildcardType.getTypeParameter()); - - mapped.setUpperBound(capturedWildcardType.getUpperBound(false).accept(this)); - - return mapped; - } - } } private static boolean isTypeCastSemantic(PsiTypeCastExpression typeCast) { 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 65546943498b..ec4883ad4ae9 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 @@ -31,6 +31,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Objects; public class PsiDiamondTypeUtil { private static final Logger LOG = Logger.getInstance(PsiDiamondTypeUtil.class); @@ -180,13 +181,15 @@ public class PsiDiamondTypeUtil { boolean constructorRef, @Nullable PsiMethod method, PsiTypeParameter[] typeParameters) { + PsiElement encoded = null; try { final PsiElement copy; final PsiType typeByParent = PsiTypesUtil.getExpectedTypeByParent(context); - if (typeByParent != null) { + if (typeByParent != null && PsiTypesUtil.isDenotableType(typeByParent, context)) { if (isAugmented(context)) { return false; } + RecaptureTypeMapper.encode(encoded = context); copy = LambdaUtil.copyWithExpectedType(context, typeByParent); } else { @@ -195,6 +198,7 @@ public class PsiDiamondTypeUtil { PsiTreeUtil.mark(argumentList != null ? argumentList : context, marker); final PsiCall call = LambdaUtil.treeWalkUp(context); if (call != null) { + RecaptureTypeMapper.encode(encoded = call); final PsiCall callCopy = LambdaUtil.copyTopLevelCall(call); copy = callCopy != null ? PsiTreeUtil.releaseMark(callCopy, marker) : null; } @@ -245,15 +249,24 @@ public class PsiDiamondTypeUtil { PsiCallExpression newParentCall = exprCopy != null ? PsiTreeUtil.getParentOfType(exprCopy, PsiCallExpression.class) : null; PsiCallExpression oldParentCall = PsiTreeUtil.getParentOfType(context, PsiCallExpression.class); - if (newParentCall != null && oldParentCall != null && - !newParentCall.resolveMethodGenerics().equals(oldParentCall.resolveMethodGenerics())) { - return false; + if (newParentCall != null && oldParentCall != null) { + JavaResolveResult newResult = newParentCall.resolveMethodGenerics(); + JavaResolveResult oldResult = oldParentCall.resolveMethodGenerics(); + if (!Objects.equals(newResult.getElement(), oldResult.getElement()) || + !new RecaptureTypeMapper().recapture(newResult.getSubstitutor()).equals(oldResult.getSubstitutor())) { + return false; + } } } catch (IncorrectOperationException e) { LOG.info(e); return false; } + finally { + if (encoded != null) { + RecaptureTypeMapper.clean(encoded); + } + } return true; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/RecaptureTypeMapper.java b/java/java-psi-impl/src/com/intellij/psi/impl/RecaptureTypeMapper.java new file mode 100644 index 000000000000..246449229092 --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/RecaptureTypeMapper.java @@ -0,0 +1,80 @@ +// 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. +package com.intellij.psi.impl; + +import com.intellij.openapi.util.Key; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.PsiImmediateClassType; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Set; + +public class RecaptureTypeMapper extends PsiTypeMapper { + public static final Key SELF_REFERENCE = Key.create("SELF_REFERENCE"); + private final Set myVisited = ContainerUtil.newIdentityTroveSet(); + + @Override + public PsiType visitType(@NotNull PsiType type) { + return type; + } + + @Override + public PsiType visitClassType(@NotNull PsiClassType classType) { + if (!myVisited.add(classType)) return classType; + final PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics(); + final PsiClass psiClass = classResolveResult.getElement(); + final PsiSubstitutor substitutor = classResolveResult.getSubstitutor(); + if (psiClass == null) return classType; + return new PsiImmediateClassType(psiClass, recapture(substitutor)); + } + + public PsiSubstitutor recapture(PsiSubstitutor substitutor) { + PsiSubstitutor result = PsiSubstitutor.EMPTY; + for (Map.Entry entry : substitutor.getSubstitutionMap().entrySet()) { + PsiType value = entry.getValue(); + result = result.put(entry.getKey(), value == null ? null : mapType(value)); + } + return result; + } + + @Override + public PsiType visitCapturedWildcardType(@NotNull PsiCapturedWildcardType capturedWildcardType) { + PsiElement context = capturedWildcardType.getContext(); + @Nullable PsiElement original = context.getCopyableUserData(SELF_REFERENCE); + if (original != null) { + context = original; + } + PsiCapturedWildcardType mapped = + PsiCapturedWildcardType.create(capturedWildcardType.getWildcard(), context, capturedWildcardType.getTypeParameter()); + + mapped.setUpperBound(capturedWildcardType.getUpperBound(false).accept(this)); + + return mapped; + } + + public static void encode(PsiElement expression) { + expression.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitElement(@NotNull PsiElement element) { + if (element instanceof PsiExpression) { + element.putCopyableUserData(SELF_REFERENCE, element); + } + super.visitElement(element); + } + }); + } + + public static void clean(PsiElement expression) { + expression.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitElement(@NotNull PsiElement element) { + if (element instanceof PsiExpression) { + element.putCopyableUserData(SELF_REFERENCE, null); + } + super.visitElement(element); + } + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/afterRecaptured.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/afterRecaptured.java new file mode 100644 index 000000000000..d146ded440a7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/afterRecaptured.java @@ -0,0 +1,17 @@ +// "Replace with <>" "true" +interface I { + I then(T t); +} +class J {} +class MyTest { + + I when(K p) { return null;} + + + + void m(J l){ + + when(l).then((J) new J<>()); + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/beforeRecaptured.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/beforeRecaptured.java new file mode 100644 index 000000000000..4bd40f5e2d0a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/explicit2diamond/beforeRecaptured.java @@ -0,0 +1,17 @@ +// "Replace with <>" "true" +interface I { + I then(T t); +} +class J {} +class MyTest { + + I when(K p) { return null;} + + + + void m(J l){ + + when(l).then((J)new Jing>()); + } + +} \ No newline at end of file