From 3bf36654d01a41ad7cb5836746e30f017f7bc3d3 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Wed, 4 Jun 2025 16:33:52 +0200 Subject: [PATCH] Java: fix replacing tail recursion with iteration failure caused by conversion to ModCommand (IDEA-371968) GitOrigin-RevId: 1d05dc8583221fb8583828847720b07be1d5bdd3 --- .../performance/TailRecursionInspection.java | 24 ++++++++++--------- .../ClassInOtherFile.after.java | 20 ++++++++++++++++ .../tail_recursion/ClassInOtherFile.java | 15 ++++++++++++ .../TailRecursionInspectionFixTest.java | 14 ++++++++++- 4 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 java/java-tests/testData/ig/com/siyeh/igfixes/performance/tail_recursion/ClassInOtherFile.after.java create mode 100644 java/java-tests/testData/ig/com/siyeh/igfixes/performance/tail_recursion/ClassInOtherFile.java diff --git a/java/java-impl/src/com/siyeh/ig/performance/TailRecursionInspection.java b/java/java-impl/src/com/siyeh/ig/performance/TailRecursionInspection.java index feeb43726327..9cffd32be88b 100644 --- a/java/java-impl/src/com/siyeh/ig/performance/TailRecursionInspection.java +++ b/java/java-impl/src/com/siyeh/ig/performance/TailRecursionInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2018 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2025 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,22 +78,22 @@ public final class TailRecursionInspection extends BaseInspection implements Cle } @Override - protected void applyFix(@NotNull Project project, @NotNull PsiElement tailCallToken, @NotNull ModPsiUpdater updater) { - final PsiMethod method = - PsiTreeUtil.getParentOfType(tailCallToken, PsiMethod.class, true, PsiClass.class, PsiLambdaExpression.class); + protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) { + PsiFile originalFile = updater.getOriginalFile(element.getContainingFile()); + final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiClass.class, PsiLambdaExpression.class); if (method == null) { return; } - final PsiCodeBlock body = method.getBody(); - if (body == null) { + PsiMethod originalMethod = PsiTreeUtil.findSameElementInCopy(method, originalFile); + final PsiCodeBlock originalBody = originalMethod.getBody(); + if (originalBody == null) { return; } - final @NonNls StringBuilder builder = new StringBuilder(); - builder.append('{'); final PsiClass containingClass = method.getContainingClass(); if (containingClass == null) { return; } + final @NonNls StringBuilder builder = new StringBuilder("{"); final String thisVariableName; final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project); if (methodReturnsContainingClassType(method, containingClass)) { @@ -101,7 +101,7 @@ public final class TailRecursionInspection extends BaseInspection implements Cle thisVariableName = styleManager.suggestUniqueVariableName("result", method, false); builder.append(' ').append(thisVariableName).append(" = this;"); } - else if (methodContainsCallOnOtherInstance(method)) { + else if (methodContainsCallOnOtherInstance(originalMethod)) { builder.append(containingClass.getName()); thisVariableName = styleManager.suggestUniqueVariableName("other", method, false); builder.append(' ').append(thisVariableName).append(" = this;"); @@ -110,7 +110,7 @@ public final class TailRecursionInspection extends BaseInspection implements Cle thisVariableName = null; } final boolean tailCallIsContainedInLoop; - if (ControlFlowUtils.isInLoop(tailCallToken)) { + if (ControlFlowUtils.isInLoop(element)) { tailCallIsContainedInLoop = true; builder.append(method.getName()).append(':'); } @@ -119,13 +119,15 @@ public final class TailRecursionInspection extends BaseInspection implements Cle } builder.append("while(true)"); final boolean methodMayCompleteNormally = ControlFlowUtils.methodMayCompleteNormally(method); - replaceTailCalls(body, method, thisVariableName, tailCallIsContainedInLoop, methodMayCompleteNormally, builder); + replaceTailCalls(originalBody, originalMethod, thisVariableName, tailCallIsContainedInLoop, methodMayCompleteNormally, builder); if (methodMayCompleteNormally) { builder.insert(builder.length() - 1, "return;"); } builder.append('}'); final PsiCodeBlock block = JavaPsiFacade.getElementFactory(project).createCodeBlockFromText(builder.toString(), method); removeEmptyElse(block); + PsiCodeBlock body = method.getBody(); + assert body != null; CodeStyleManager.getInstance(project).reformat(body.replace(block)); } diff --git a/java/java-tests/testData/ig/com/siyeh/igfixes/performance/tail_recursion/ClassInOtherFile.after.java b/java/java-tests/testData/ig/com/siyeh/igfixes/performance/tail_recursion/ClassInOtherFile.after.java new file mode 100644 index 000000000000..576ed09499cd --- /dev/null +++ b/java/java-tests/testData/ig/com/siyeh/igfixes/performance/tail_recursion/ClassInOtherFile.after.java @@ -0,0 +1,20 @@ +class ClassInOtherFile { + private final Container parent; + private boolean boundsValid; + + public Visual(Container parent) { + this.parent = parent; + } + + void invalidate() { + ClassInOtherFile other = this; + while (true) { + other.boundsValid = false; + if (other.parent != null) { + other = other.parent; + continue; + } + return; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/ig/com/siyeh/igfixes/performance/tail_recursion/ClassInOtherFile.java b/java/java-tests/testData/ig/com/siyeh/igfixes/performance/tail_recursion/ClassInOtherFile.java new file mode 100644 index 000000000000..d9986308cde8 --- /dev/null +++ b/java/java-tests/testData/ig/com/siyeh/igfixes/performance/tail_recursion/ClassInOtherFile.java @@ -0,0 +1,15 @@ +class ClassInOtherFile { + private final Container parent; + private boolean boundsValid; + + public Visual(Container parent) { + this.parent = parent; + } + + void invalidate() { + boundsValid = false; + if (parent != null) { + parent.invalidate(); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/siyeh/ig/fixes/performance/TailRecursionInspectionFixTest.java b/java/java-tests/testSrc/com/siyeh/ig/fixes/performance/TailRecursionInspectionFixTest.java index dfd33312fbfe..d580d24fc12a 100644 --- a/java/java-tests/testSrc/com/siyeh/ig/fixes/performance/TailRecursionInspectionFixTest.java +++ b/java/java-tests/testSrc/com/siyeh/ig/fixes/performance/TailRecursionInspectionFixTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2018 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. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.siyeh.ig.fixes.performance; import com.intellij.application.options.CodeStyle; @@ -34,6 +34,18 @@ public class TailRecursionInspectionFixTest extends IGQuickFixesTestCase { public void testVoidMethod1() { doTest(); } public void testVoidMethod2() { doTest(); } public void testAndOrChain() { doTest(); } + + public void testClassInOtherFile() { + myFixture.addClass(""" + class Container extends ClassInOtherFile { + Container(Container parent) { + super(parent); + } + } + """); + doTest(); + } + public void testAndOrChain2() { CommonCodeStyleSettings settings = CodeStyle.getSettings(getProject()).getCommonSettings(JavaLanguage.INSTANCE); int oldValue = settings.IF_BRACE_FORCE;