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 ee85a52f9cb3..951d6f42fe1d 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 @@ -21,7 +21,10 @@ import com.siyeh.ig.psiutils.ExpressionUtils; 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.Set; public class RedundantCastUtil { private static final Logger LOG = Logger.getInstance(RedundantCastUtil.class); @@ -213,63 +216,9 @@ public class RedundantCastUtil { public void visitMethodCallExpression(PsiMethodCallExpression expression) { processCall(expression); - checkForVirtual(expression); super.visitMethodCallExpression(expression); } - private void checkForVirtual(PsiMethodCallExpression methodCall) { - PsiReferenceExpression methodExpr = methodCall.getMethodExpression(); - PsiExpression qualifier = methodExpr.getQualifierExpression(); - if (!(qualifier instanceof PsiParenthesizedExpression)) return; - PsiExpression operand = deparenthesizeExpression(qualifier); - if (!(operand instanceof PsiTypeCastExpression)) return; - PsiTypeCastExpression typeCast = (PsiTypeCastExpression)operand; - PsiExpression castOperand = typeCast.getOperand(); - if (castOperand == null) return; - - PsiType type = castOperand.getType(); - if (type == null) return; - if (type instanceof PsiPrimitiveType) return; - - final JavaResolveResult resolveResult = methodExpr.advancedResolve(false); - PsiMethod targetMethod = (PsiMethod)resolveResult.getElement(); - if (targetMethod == null) return; - if (targetMethod.hasModifierProperty(PsiModifier.STATIC)) return; - - try { - Project project = methodExpr.getProject(); - PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); - - final PsiExpression expressionFromText = factory.createExpressionFromText(methodCall.getText(), methodCall); - if (!(expressionFromText instanceof PsiMethodCallExpression)) return; - PsiMethodCallExpression newCall = (PsiMethodCallExpression)expressionFromText; - PsiExpression newQualifier = newCall.getMethodExpression().getQualifierExpression(); - PsiTypeCastExpression newCast = Objects.requireNonNull((PsiTypeCastExpression)deparenthesizeExpression(newQualifier)); - PsiExpression newOperand = Objects.requireNonNull(newCast.getOperand()); - newQualifier.replace(newOperand); - - final JavaResolveResult newResult = newCall.getMethodExpression().advancedResolve(false); - if (!newResult.isValidResult()) return; - final PsiMethod newTargetMethod = (PsiMethod)newResult.getElement(); - PsiType newReturnType = newCall.getType(); - PsiType oldReturnType = methodCall.getType(); - if (newReturnType instanceof PsiCapturedWildcardType && oldReturnType instanceof PsiCapturedWildcardType) { - newReturnType = ((PsiCapturedWildcardType)newReturnType).getUpperBound(); - oldReturnType = ((PsiCapturedWildcardType)oldReturnType).getUpperBound(); - } - if (Comparing.equal(newReturnType, oldReturnType) && - (Comparing.equal(newTargetMethod, targetMethod) || - !(newTargetMethod.isDeprecated() && !targetMethod.isDeprecated()) && - MethodSignatureUtil.isSuperMethod(newTargetMethod, targetMethod) && - // see SCR11555, SCR14559 - areThrownExceptionsCompatible(targetMethod, newTargetMethod) && - areNullabilityCompatible(targetMethod, newTargetMethod))) { - addToResults(typeCast); - } - } - catch (IncorrectOperationException ignore) { } - } - private static boolean areNullabilityCompatible(final PsiMethod oldTargetMethod, final PsiMethod newTargetMethod) { // the cast may be for the @NotNull which newTargetMethod has whereas the oldTargetMethod doesn't @@ -307,31 +256,87 @@ public class RedundantCastUtil { @Override public void visitReferenceExpression(PsiReferenceExpression expression) { - if (!(expression.getParent() instanceof PsiMethodCallExpression)) { - PsiExpression qualifier = deparenthesizeExpression(expression.getQualifierExpression()); - if (qualifier instanceof PsiTypeCastExpression) { - PsiExpression operand = deparenthesizeExpression(((PsiTypeCastExpression)qualifier).getOperand()); - if (operand == null) return; - PsiTypeElement typeElement = ((PsiTypeCastExpression)qualifier).getCastType(); - if (typeElement == null) return; - PsiType opType = operand.getType(); - if (operand instanceof PsiFunctionalExpression) return; - if (typeElement.getType() instanceof PsiClassType && opType instanceof PsiPrimitiveType) return; //explicit boxing - //Check accessibility - if (opType instanceof PsiClassType) { - PsiElement element = expression.resolve(); - if (!(element instanceof PsiMember)) return; - PsiClass accessClass = ((PsiClassType)opType).resolve(); - if (accessClass == null) return; - if (!JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper().isAccessible((PsiMember)element, qualifier, accessClass)) return; - if (!isCastRedundantInRefExpression(expression, operand)) return; - addIfNarrowing((PsiTypeCastExpression)qualifier, opType, null); - } + PsiExpression qualifier = deparenthesizeExpression(expression.getQualifierExpression()); + if (qualifier instanceof PsiTypeCastExpression) { + PsiTypeCastExpression typeCast = (PsiTypeCastExpression)qualifier; + PsiExpression operand = deparenthesizeExpression(typeCast.getOperand()); + while (operand instanceof PsiTypeCastExpression) { + operand = deparenthesizeExpression(((PsiTypeCastExpression)operand).getOperand()); + } + if (operand == null) return; + + PsiTypeElement typeElement = typeCast.getCastType(); + if (typeElement == null) return; + PsiType opType = operand.getType(); + if (opType == null) return; + + if (!(operand instanceof PsiFunctionalExpression || opType instanceof PsiPrimitiveType) && + isCastInReferenceQualifierRedundant(expression, operand)) { + addToResults(typeCast); } } super.visitReferenceExpression(expression); } + private static boolean isCastInReferenceQualifierRedundant(final PsiReferenceExpression refExpression, + final PsiExpression castOperand) { + final JavaResolveResult resolveResult = refExpression.advancedResolve(false); + PsiElement oldMember = resolveResult.getElement(); + if (oldMember == null) return false; + try { + PsiElement parent = PsiUtil.skipParenthesizedExprUp(refExpression.getParent()); + if (parent instanceof PsiMethodCallExpression) { + PsiMethod targetMethod = (PsiMethod)oldMember; + if (targetMethod.hasModifierProperty(PsiModifier.STATIC)) { + return false; + } + PsiMethodCallExpression newCall = + (PsiMethodCallExpression)copyCallExpression(((PsiMethodCallExpression)parent), PsiTypesUtil.getExpectedTypeByParent(parent)); + if (newCall == null) return false; + PsiExpression newQualifier = newCall.getMethodExpression().getQualifierExpression(); + LOG.assertTrue(newQualifier != null); + newQualifier.replace(castOperand); + + final JavaResolveResult newResult = newCall.getMethodExpression().advancedResolve(false); + if (!newResult.isValidResult()) return false; + + final PsiMethod newTargetMethod = (PsiMethod)newResult.getElement(); + PsiType newReturnType = newCall.getType(); + PsiType oldReturnType = ((PsiMethodCallExpression)parent).getType(); + if (newReturnType instanceof PsiCapturedWildcardType && oldReturnType instanceof PsiCapturedWildcardType) { + oldReturnType = ((PsiCapturedWildcardType)oldReturnType).getUpperBound(); + newReturnType = ((PsiCapturedWildcardType)newReturnType).getUpperBound(); + } + + if (Comparing.equal(PsiUtil.recaptureWildcards(newReturnType, parent), oldReturnType) && + (Comparing.equal(newTargetMethod, targetMethod) || + !(newTargetMethod.isDeprecated() && !targetMethod.isDeprecated()) && + MethodSignatureUtil.isSuperMethod(newTargetMethod, targetMethod) && + // see SCR11555, SCR14559 + areThrownExceptionsCompatible(targetMethod, newTargetMethod) && + areNullabilityCompatible(targetMethod, newTargetMethod))) { + return true; + } + return false; + } + else { + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(castOperand.getProject()); + final PsiReferenceExpression newExpression = (PsiReferenceExpression)elementFactory.createExpressionFromText(refExpression.getText(), castOperand); + final PsiExpression newQualifier = newExpression.getQualifierExpression(); + LOG.assertTrue(newQualifier != null); + newQualifier.replace(castOperand); + + JavaResolveResult newResult = newExpression.advancedResolve(false); + if (!newResult.isValidResult()) return false; + + return oldMember.equals(newResult.getElement()); + } + } + catch (IncorrectOperationException ignore) { + return false; + } + } + private void processCall(PsiCall expression){ PsiExpressionList argumentList = expression.getArgumentList(); if (argumentList == null) return; @@ -874,26 +879,7 @@ public class RedundantCastUtil { super.visitArrayAccessExpression(expression); } } - - private static boolean isCastRedundantInRefExpression (final PsiReferenceExpression refExpression, final PsiExpression castOperand) { - if (refExpression.getParent() instanceof PsiMethodCallExpression) return false; - final PsiElement resolved = refExpression.resolve(); - try { - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(refExpression.getProject()); - final PsiExpression copyExpression = elementFactory.createExpressionFromText(refExpression.getText(), refExpression); - if (copyExpression instanceof PsiReferenceExpression) { - final PsiReferenceExpression copy = (PsiReferenceExpression)copyExpression; - final PsiExpression qualifier = copy.getQualifierExpression(); - if (qualifier != null) { - qualifier.replace(castOperand); - return copy.resolve() == resolved; - } - } - } - catch (IncorrectOperationException ignore) { } - return false; - } - + private static boolean isTypeCastSemantic(PsiTypeCastExpression typeCast) { PsiExpression operand = typeCast.getOperand(); if (operand == null) return false; diff --git a/java/java-tests/testData/inspection/redundantCast/Inaccessible.java b/java/java-tests/testData/inspection/redundantCast/Inaccessible.java index 4c36d5bd0c28..686c1e846d7d 100644 --- a/java/java-tests/testData/inspection/redundantCast/Inaccessible.java +++ b/java/java-tests/testData/inspection/redundantCast/Inaccessible.java @@ -2,6 +2,7 @@ class Entry { private final Object obj; protected Entry(Directory parent) { obj = ((Entry)parent).obj; //cast is needed because 'obj' is not visible with 'Directory' access class + String s = ((Object)((Entry)(Entry)parent).obj).toString(); } } diff --git a/java/java-tests/testData/inspection/redundantCast/generics/QualifierWithCapture.java b/java/java-tests/testData/inspection/redundantCast/generics/QualifierWithCapture.java new file mode 100644 index 000000000000..611e9d94736f --- /dev/null +++ b/java/java-tests/testData/inspection/redundantCast/generics/QualifierWithCapture.java @@ -0,0 +1,25 @@ +import java.util.*; + +class MyTest { + void m(List l) { + int b = ((F) l.get(0)).i; + } + + static class F { + int i; + } + + List getWildcard() { + return Collections.emptyList(); + } + + List getConcrete() { + return Collections.emptyList(); + } + + void test() { + ((MyTest)this).getConcrete(); + ((MyTest)this).getWildcard(); + } + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/RedundantCast15Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/RedundantCast15Test.java index 007eb49a87f1..87f6b22d1d6c 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/RedundantCast15Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/RedundantCast15Test.java @@ -58,6 +58,7 @@ public class RedundantCast15Test extends LightJavaCodeInsightFixtureTestCase { public void testCaseThrowable() { doTest();} public void testSafeTempVarName() { doTest();} public void testBinaryComparison() { doTest();} + public void testQualifierWithCapture() { doTest();} public void testTypeParameterAccessChecksJava7() { doTest();