diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/CastCanBeReplacedWithVariableInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/CastCanBeReplacedWithVariableInspection.java index 775a13bcccbd..107309642b1d 100644 --- a/java/java-impl-inspections/src/com/intellij/codeInspection/CastCanBeReplacedWithVariableInspection.java +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/CastCanBeReplacedWithVariableInspection.java @@ -72,6 +72,7 @@ public class CastCanBeReplacedWithVariableInspection extends AbstractBaseJavaLoc final PsiCodeBlock methodBody = method.getBody(); if (methodBody == null) return null; final TextRange expressionTextRange = expression.getTextRange(); + if (expressionTextRange == null) return null; PsiExpression operand = PsiUtil.skipParenthesizedExprDown(expression.getOperand()); if (operand == null) return null; PsiType castType = expressionCastType.getType(); @@ -86,11 +87,7 @@ public class CastCanBeReplacedWithVariableInspection extends AbstractBaseJavaLoc .toList(); PsiResolveHelper resolveHelper = PsiResolveHelper.getInstance(method.getProject()); for (PsiTypeCastExpression occurrence : found) { - ProgressIndicatorProvider.checkCanceled(); - final TextRange occurrenceTextRange = occurrence.getTextRange(); - if (occurrence == expression || occurrenceTextRange.getEndOffset() >= expressionTextRange.getStartOffset()) { - continue; - } + if (!isAtRightLocation(expression, expressionTextRange, occurrence)) continue; final PsiLocalVariable variable = getVariable(occurrence); @@ -113,7 +110,53 @@ public class CastCanBeReplacedWithVariableInspection extends AbstractBaseJavaLoc } } - return null; + List narrowVariables = + SyntaxTraverser.psiTraverser(method) + .filter(PsiAssignmentExpression.class) + .filter(assignment -> EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(assignment.getLExpression(), operand)) + .filter(assignment -> { + PsiExpression narrowVariable = assignment.getRExpression(); + return narrowVariable != null && narrowVariable.getType() != null && InstanceOfUtils.typeCompatible(narrowVariable.getType(), castType, operand); + }) + .toList(); + for (PsiAssignmentExpression narrowVariable : narrowVariables) { + if (!isAtRightLocation(expression, expressionTextRange, narrowVariable)) continue; + + final PsiLocalVariable declaration = getVariable(narrowVariable); + + if (declaration != null && + resolveHelper.resolveReferencedVariable(declaration.getName(), expression) == declaration && + !isChangedBetween(castedVar, methodBody, narrowVariable, expression) && + !isChangedBetween(declaration, methodBody, narrowVariable, expression)) { + return declaration; + } + } + + if (!(operand instanceof PsiReferenceExpression wideReferenceExpression) + || !(wideReferenceExpression.resolve() instanceof PsiLocalVariable wideVariable)) return null; + + PsiExpression narrowExpression = wideVariable.getInitializer(); + if (!(narrowExpression instanceof PsiReferenceExpression narrowReferenceExpression) + || !(narrowReferenceExpression.resolve() instanceof PsiVariable narrowVariable) + || narrowExpression.getType() == null + || !InstanceOfUtils.typeCompatible(narrowExpression.getType(), castType, operand)) return null; + + if (!isAtRightLocation(expression, expressionTextRange, narrowExpression)) return null; + + if (narrowVariable.getName() == null + || resolveHelper.resolveReferencedVariable(narrowVariable.getName(), expression) != narrowVariable + || isChangedBetween(castedVar, methodBody, narrowExpression, expression) + || isChangedBetween(narrowVariable, methodBody, narrowExpression, expression)) return null; + + return narrowVariable; + } + + private static boolean isAtRightLocation(@NotNull PsiTypeCastExpression expression, + @NotNull TextRange expressionTextRange, + @NotNull PsiExpression occurrence) { + ProgressIndicatorProvider.checkCanceled(); + final TextRange occurrenceTextRange = occurrence.getTextRange(); + return occurrence != expression && occurrenceTextRange.getEndOffset() < expressionTextRange.getStartOffset(); } private static boolean isChangedBetween(@NotNull final PsiVariable variable, @@ -163,7 +206,7 @@ public class CastCanBeReplacedWithVariableInspection extends AbstractBaseJavaLoc } @Nullable - private static PsiLocalVariable getVariable(@NotNull PsiExpression occurrence) { + private static PsiLocalVariable getVariable(@NotNull PsiTypeCastExpression occurrence) { final PsiElement parent = PsiUtil.skipParenthesizedExprUp(occurrence.getParent()); if (parent instanceof PsiLocalVariable localVariable) { @@ -179,6 +222,16 @@ public class CastCanBeReplacedWithVariableInspection extends AbstractBaseJavaLoc return null; } + @Nullable + private static PsiLocalVariable getVariable(@NotNull PsiAssignmentExpression occurrence) { + if (PsiUtil.skipParenthesizedExprDown(occurrence.getRExpression()) instanceof PsiReferenceExpression referenceExpression && + referenceExpression.resolve() instanceof PsiLocalVariable localVariable) { + return localVariable; + } + + return null; + } + private static class ReplaceCastWithVariableFix implements LocalQuickFix { private final @NotNull String myText; private final @NotNull String myVariableName; diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterParentherized.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterParentherized.java new file mode 100644 index 000000000000..a371dd042244 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterParentherized.java @@ -0,0 +1,11 @@ +// "Replace '(String) aText' with 'anActualText'" "true" + +class FooBar { + void method() { + String anActualText = "Hello World! "; + Object aText; + aText = (anActualText); + System.out.println(anActualText.trim()); + aText = Integer.MAX_VALUE; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterWideningAssignment.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterWideningAssignment.java new file mode 100644 index 000000000000..c1fe356e7487 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterWideningAssignment.java @@ -0,0 +1,11 @@ +// "Replace '(String) aText' with 'anActualText'" "true" + +class FooBar { + void method() { + String anActualText = "Hello World! "; + Object aText; + aText = anActualText; + System.out.println(anActualText.trim()); + aText = Integer.MAX_VALUE; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterWideningDeclaration.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterWideningDeclaration.java new file mode 100644 index 000000000000..3ee82a793922 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/afterWideningDeclaration.java @@ -0,0 +1,10 @@ +// "Replace '(String) aText' with 'anActualText'" "true" + +class FooBar { + void method() { + String anActualText = "Hello World! "; + Object aText = anActualText; + System.out.println(anActualText.trim()); + aText = Integer.MAX_VALUE; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeParentherized.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeParentherized.java new file mode 100644 index 000000000000..d1b76503891f --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeParentherized.java @@ -0,0 +1,11 @@ +// "Replace '(String) aText' with 'anActualText'" "true" + +class FooBar { + void method() { + String anActualText = "Hello World! "; + Object aText; + aText = (anActualText); + System.out.println(((String) aText).trim()); + aText = Integer.MAX_VALUE; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeWideningAssignment.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeWideningAssignment.java new file mode 100644 index 000000000000..a06d8ed8d783 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeWideningAssignment.java @@ -0,0 +1,11 @@ +// "Replace '(String) aText' with 'anActualText'" "true" + +class FooBar { + void method() { + String anActualText = "Hello World! "; + Object aText; + aText = anActualText; + System.out.println(((String) aText).trim()); + aText = Integer.MAX_VALUE; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeWideningDeclaration.java b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeWideningDeclaration.java new file mode 100644 index 000000000000..79b8dd0b09d0 --- /dev/null +++ b/java/java-tests/testData/inspection/castCanBeReplacedWithVariable/beforeWideningDeclaration.java @@ -0,0 +1,10 @@ +// "Replace '(String) aText' with 'anActualText'" "true" + +class FooBar { + void method() { + String anActualText = "Hello World! "; + Object aText = anActualText; + System.out.println(((String) aText).trim()); + aText = Integer.MAX_VALUE; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableFixTest.java similarity index 90% rename from java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableInspectionTest.java rename to java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableFixTest.java index bf03217b056b..9212808d708c 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/CastCanBeReplacedWithVariableFixTest.java @@ -8,7 +8,7 @@ import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; import org.jetbrains.annotations.NotNull; -public class CastCanBeReplacedWithVariableInspectionTest extends LightQuickFixParameterizedTestCase { +public class CastCanBeReplacedWithVariableFixTest extends LightQuickFixParameterizedTestCase { @Override protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() { return new LocalInspectionTool[]{new CastCanBeReplacedWithVariableInspection()};