From c72fcabc652cfd26e3a2e4bebc8afbaabe87a893 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 9 Mar 2026 14:14:27 +0100 Subject: [PATCH 1/2] Fix `IfElseIfConstructToSwitch` dropping pattern variables for unresolvable types When JavaTemplate can't resolve a non-JDK type during switch case generation, it may not produce a J.VariableDeclarations AST node, causing pattern variable declarations to be dropped (e.g., `case Dog d ->` becomes `case Dog ->`). Fix by using positional case identification instead of type-checking the label, and reconstructing a proper J.VariableDeclarations from the original J.InstanceOf when the template parser fails to produce one. Fixes #1004 --- .../lang/IfElseIfConstructToSwitch.java | 85 ++++++++++++++----- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/IfElseIfConstructToSwitch.java b/src/main/java/org/openrewrite/java/migrate/lang/IfElseIfConstructToSwitch.java index f75cbbf9d3..1704e84743 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/IfElseIfConstructToSwitch.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/IfElseIfConstructToSwitch.java @@ -27,8 +27,10 @@ import org.openrewrite.java.search.UsesJavaVersion; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JRightPadded; import org.openrewrite.java.tree.Space; import org.openrewrite.java.tree.Statement; +import org.openrewrite.marker.Markers; import org.openrewrite.staticanalysis.groovy.GroovyFileChecker; import org.openrewrite.staticanalysis.kotlin.KotlinFileChecker; @@ -225,36 +227,79 @@ public J.Return visitReturn(J.Return return_, AtomicBoolean atomicBoolean) { * JavaTemplate uses raw string substitution (#{}), which loses type information * for non-JDK types. This method restores the original type information from the * instanceof checks onto the generated switch case labels. + * When the type is completely unresolvable, JavaTemplate may not produce a + * J.VariableDeclarations at all, so we reconstruct one from the original instanceof. */ private J.Switch fixTypeAttribution(J.Switch switch_) { Iterator instanceOfs = patternMatchers.keySet().iterator(); + // Cases are ordered: [null case (optional)], [pattern cases...], [default case] + int nullCaseOffset = nullCheckedParameter != null ? 1 : 0; + int patternCaseCount = patternMatchers.size(); + int[] index = {0}; return switch_.withCases(switch_.getCases().withStatements( ListUtils.map(switch_.getCases().getStatements(), stmt -> { - if (stmt instanceof J.Case && instanceOfs.hasNext()) { - J.Case case_ = (J.Case) stmt; - if (!case_.getCaseLabels().isEmpty() && case_.getCaseLabels().get(0) instanceof J.VariableDeclarations) { - J.InstanceOf instanceOf = instanceOfs.next(); - J.VariableDeclarations varDecl = (J.VariableDeclarations) case_.getCaseLabels().get(0); - // Replace typeExpression with the original clazz (which has proper type info) - varDecl = varDecl.withTypeExpression( - varDecl.getTypeExpression() != null ? - instanceOf.getClazz().withPrefix(varDecl.getTypeExpression().getPrefix()) : - instanceOf.getClazz().withPrefix(Space.EMPTY)); - // Fix variable type from original pattern - if (instanceOf.getPattern() instanceof J.Identifier && !varDecl.getVariables().isEmpty()) { - J.Identifier originalPattern = (J.Identifier) instanceOf.getPattern(); - J.VariableDeclarations.NamedVariable var0 = varDecl.getVariables().get(0); - varDecl = varDecl.withVariables(singletonList( - var0.withType(originalPattern.getType()) - .withName(var0.getName().withType(originalPattern.getType())))); - } - return case_.withCaseLabels(singletonList(varDecl.withPrefix(case_.getCaseLabels().get(0).getPrefix()))); + if (!(stmt instanceof J.Case)) { + return stmt; + } + int currentIndex = index[0]++; + int patternIndex = currentIndex - nullCaseOffset; + if (patternIndex < 0 || patternIndex >= patternCaseCount || !instanceOfs.hasNext()) { + return stmt; // null case or default case + } + J.Case case_ = (J.Case) stmt; + J.InstanceOf instanceOf = instanceOfs.next(); + J label = case_.getCaseLabels().get(0); + + if (label instanceof J.VariableDeclarations) { + J.VariableDeclarations varDecl = (J.VariableDeclarations) label; + // Replace typeExpression with the original clazz (which has proper type info) + varDecl = varDecl.withTypeExpression( + varDecl.getTypeExpression() != null ? + instanceOf.getClazz().withPrefix(varDecl.getTypeExpression().getPrefix()) : + instanceOf.getClazz().withPrefix(Space.EMPTY)); + // Fix variable type from original pattern + if (instanceOf.getPattern() instanceof J.Identifier && !varDecl.getVariables().isEmpty()) { + J.Identifier originalPattern = (J.Identifier) instanceOf.getPattern(); + J.VariableDeclarations.NamedVariable var0 = varDecl.getVariables().get(0); + varDecl = varDecl.withVariables(singletonList( + var0.withType(originalPattern.getType()) + .withName(var0.getName().withType(originalPattern.getType())))); } + return case_.withCaseLabels(singletonList(varDecl.withPrefix(label.getPrefix()))); + } else { + // JavaTemplate couldn't resolve the type, so no VariableDeclarations was produced. + // Reconstruct one from the original instanceof pattern. + return case_.withCaseLabels(singletonList( + buildVariableDeclarations(instanceOf, label.getPrefix()))); } - return stmt; }))); } + @SuppressWarnings("deprecation") + private J.VariableDeclarations buildVariableDeclarations(J.InstanceOf instanceOf, Space prefix) { + J.Identifier pattern = (J.Identifier) instanceOf.getPattern(); + J.VariableDeclarations.NamedVariable namedVar = new J.VariableDeclarations.NamedVariable( + Tree.randomId(), + Space.SINGLE_SPACE, + Markers.EMPTY, + pattern.withPrefix(Space.EMPTY), + Collections.emptyList(), + null, + null + ); + return new J.VariableDeclarations( + Tree.randomId(), + prefix, + Markers.EMPTY, + Collections.emptyList(), + Collections.emptyList(), + instanceOf.getClazz().withPrefix(Space.EMPTY), + null, + null, + singletonList(JRightPadded.build(namedVar)) + ); + } + private Optional switchOn() { return patternMatchers.keySet().stream() .map(J.InstanceOf::getExpression) From db64e4b56be0f99b697463bff11c77ce9e711bca Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 9 Mar 2026 17:33:22 +0100 Subject: [PATCH 2/2] Use `ListUtils.map` with index --- .../java/migrate/lang/IfElseIfConstructToSwitch.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/IfElseIfConstructToSwitch.java b/src/main/java/org/openrewrite/java/migrate/lang/IfElseIfConstructToSwitch.java index 1704e84743..a64899a878 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/IfElseIfConstructToSwitch.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/IfElseIfConstructToSwitch.java @@ -235,13 +235,11 @@ private J.Switch fixTypeAttribution(J.Switch switch_) { // Cases are ordered: [null case (optional)], [pattern cases...], [default case] int nullCaseOffset = nullCheckedParameter != null ? 1 : 0; int patternCaseCount = patternMatchers.size(); - int[] index = {0}; return switch_.withCases(switch_.getCases().withStatements( - ListUtils.map(switch_.getCases().getStatements(), stmt -> { + ListUtils.map(switch_.getCases().getStatements(), (currentIndex, stmt) -> { if (!(stmt instanceof J.Case)) { return stmt; } - int currentIndex = index[0]++; int patternIndex = currentIndex - nullCaseOffset; if (patternIndex < 0 || patternIndex >= patternCaseCount || !instanceOfs.hasNext()) { return stmt; // null case or default case