Skip to content

Fix ChangeType inner class import handling when outer class import is present#7032

Open
MBoegers wants to merge 3 commits intomainfrom
fix/change-type-inner-class-import
Open

Fix ChangeType inner class import handling when outer class import is present#7032
MBoegers wants to merge 3 commits intomainfrom
fix/change-type-inner-class-import

Conversation

@MBoegers
Copy link
Copy Markdown
Contributor

@MBoegers MBoegers commented Mar 18, 2026

Summary

  • When renaming an inner class (e.g. foo.A$Builderbar.B$Builder) where the source file imports the outer class (import foo.A), ChangeType now correctly force-adds import bar.B and omits the redundant import bar.B.Builder - since import bar.B already makes B.Builder accessible.
  • Fixes the case where the new outer class import was silently dropped, leaving the code with an unresolved reference.
  • Adds an owningClassSame helper to detect when only the package changes (not the outer class name), enabling AddImport to decide based on actual type references in that case.

Test plan

  • changeTypeOfInnerClass - inner class renamed within same outer class name, different package: only outer class import added
  • changeTypeOfInnerCompletely - inner class renamed to an entirely different outer class: only new outer class import added, no redundant inner import
  • changeTypeOfInnerClassImplicitly - outer class renamed with inner class following implicitly via import

When renaming an inner class (e.g. foo.A\$Builder → bar.B\$Builder) where the
source file imports the outer class (import foo.A), ChangeType now correctly:
- Force-adds the new outer class import (import bar.B) when the old outer class
  import was removed, since the code accesses the inner class via qualified name
  (B.Builder)
- Omits the redundant inner class import (import bar.B.Builder) in this case,
  since import bar.B already makes B.Builder accessible

Also adds a test for renaming an inner class within the same outer class.
- Rename changeTypeOfInnerClassConstructor → changeTypeOfInnerClass
  (scenario: import foo.A.Builder only, rename to bar.A$Builder within same outer name)
- Rename changeTypeOfInnerClass → changeTypeOfInnerCompletely
  (scenario: rename to a completely different outer class bar.B$Builder)
- Rename renameInnerClassWithinSameOuterClass → changeTypeOfInnerClassImplicitly
  (scenario: outer class renamed, inner class implicitly follows via import)
- Add @issue annotations linking tests to the GitHub issues they cover
- Adjust test content to better represent the intent of each scenario
…er-class-import

# Conflicts:
#	rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
@MBoegers MBoegers requested a review from timtebeek April 1, 2026 13:41
Comment on lines +2584 to +2585
import foo.A;
import foo.A.Builder;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have both imports here in the test? I'd think we only need the first one right?

Comment on lines +2618 to +2624
"""
package bar;

public class B {
public static class Builder {}
}
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need this second depends on block right? As ChangeType synthesizes types for what it changes, and we only need to provide the correct types to compile the imports?

Comment on lines +2524 to +2526
void renameInnerClassWithinSameOuterClass() {
rewriteRun(
spec -> spec.recipe(new ChangeType("foo.A$Builder", "foo.A$Creator", null))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we're testing the fairly common case of A.B, but should we also test the variants for A.B.C ? I wonder if those are handled correctly for all the permutations of imported as A, A.B or A.B.C, and then changed as A, A$B or A$B$C.

// Force-add the outer class import when the original outer class import was
// explicitly removed (meaning code used the outer class by name, e.g. "B" in "B.Builder").
// When the outer class import was absent, the code uses only the simple inner class
// name (e.g. just "Y"), so AddImport can decide based on actual references.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our comments we first use B, then switch to Y here; that seems needlessly confusing, or was that intentional?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants