Skip to content

Java: support parameterized types in ChangeMethodInvocationReturnType#8043

Draft
MBoegers wants to merge 1 commit into
mainfrom
MBoegers/typeutils-before-javatypetree
Draft

Java: support parameterized types in ChangeMethodInvocationReturnType#8043
MBoegers wants to merge 1 commit into
mainfrom
MBoegers/typeutils-before-javatypetree

Conversation

@MBoegers

@MBoegers MBoegers commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Problem

ChangeMethodInvocationReturnType breaks on generic return types. JavaType.buildType("java.util.Set<java.lang.String>") doesn't understand generics, so it folds the <...> suffix into a malformed ShallowClass and the recipe emits a bare J.Identifier whose simple name still contains <...> — non-compilable output with missing imports for the type parameters.

Change

  • Add TypeUtils.buildTypeTree(String) — a single StringTypeTree builder supporting parameterized types with nested type arguments (Map<String, List<Integer>>), arrays, wildcards (?, ? extends X, ? super X), primitives and java.lang shortening. Its JavaType is resolved with the same parser, so buildTypeTree(name).getType() yields the matching type.
  • Consolidate the near-duplicate hand-rolled AddMethodParameter.createTypeTree into buildTypeTree, fixing latent gaps: depth-aware splitting of multi-argument nested generics, a space after each comma, and ? super bounds.
  • ChangeMethodInvocationReturnType now builds a proper parameterized type expression, derives the new JavaType via getType(), shortens the fully-qualified tree via ImportService (importing the raw type and every type parameter), and recursively removes imports for the replaced type. The existing initializedByMatch guard is preserved.

The core JavaType/TypeTree interfaces are intentionally left untouched; the logic lives in TypeUtils. Promoting it into JavaType.buildType later would be a clean follow-up.

Test plan

  • Added the repro (List<String>Set<String>), nested generics (Map<String, List<Integer>>), a depth-stressing case (Map<String, Map<Integer, Long>>) that the old naive split breaks, and parameterized → raw (List<String>Object).
  • ./gradlew :rewrite-java:test passes; AddMethodParameterTest, TCK TypeTreeTest, ChangeTypeTest green; licenseFormat clean.

`JavaType.buildType("java.util.Set<java.lang.String>")` does not understand
generics, so the recipe folded the `<...>` suffix into a malformed `ShallowClass`
and emitted a bare `J.Identifier` whose name still contained `<...>`, with no
imports for the type parameters - producing non-compilable output.

Add `TypeUtils.buildTypeTree(String)`, a single string -> TypeTree builder that
handles parameterized types (nested type arguments), arrays, wildcards,
primitives and `java.lang` shortening, and consolidate the near-duplicate
`AddMethodParameter.createTypeTree` into it. The recipe now builds a proper
parameterized type expression, derives its JavaType via `getType()`, shortens
the fully-qualified tree (adding imports for every nested type), and recursively
removes imports for the replaced type.

Closes #7502
Comment on lines +113 to +125
public static TypeTree buildTypeTree(String typeName) {
if (typeName.endsWith("]")) {
TypeTree elementType = buildTypeTree(typeName.substring(0, typeName.lastIndexOf('[')));
return new J.ArrayType(
randomId(),
Space.EMPTY,
Markers.EMPTY,
elementType,
null,
JLeftPadded.build(Space.EMPTY),
buildType(typeName)
);
}

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.

The challenge I have with this mechanism is that it is not classpath-aware. If you use this to build a type tree for anything not in the standard library you get a stubbed / shallow type.
Is there a reason a type tree produced by a context-insensitive JavaTemplate will not serve?

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.

ChangeMethodInvocationReturnType is not handling parameterized types

2 participants