Skip to content

Commit 1552cca

Browse files
timtebeekclaude
andcommitted
Fix UseVarForGenericMethodInvocations to handle nested generics with diamond operators
When converting to var, the recipe now makes nested generic type parameters explicit by replacing diamond operators (<>) with the actual type parameters from the variable declaration. This prevents compilation errors when using method references. Fixes #862 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 640ac12 commit 1552cca

File tree

2 files changed

+144
-9
lines changed

2 files changed

+144
-9
lines changed

src/main/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocations.java

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,21 @@
1515
*/
1616
package org.openrewrite.java.migrate.lang.var;
1717

18+
import org.jspecify.annotations.Nullable;
1819
import org.openrewrite.ExecutionContext;
1920
import org.openrewrite.Preconditions;
2021
import org.openrewrite.Recipe;
2122
import org.openrewrite.TreeVisitor;
23+
import org.openrewrite.internal.ListUtils;
2224
import org.openrewrite.java.JavaIsoVisitor;
2325
import org.openrewrite.java.search.UsesJavaVersion;
2426
import org.openrewrite.java.tree.Expression;
2527
import org.openrewrite.java.tree.J;
2628
import org.openrewrite.java.tree.JavaType;
29+
import org.openrewrite.java.tree.TypeTree;
30+
31+
import java.util.ArrayList;
32+
import java.util.List;
2733

2834
public class UseVarForGenericMethodInvocations extends Recipe {
2935
@Override
@@ -82,16 +88,65 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations v
8288
maybeRemoveImport((JavaType.FullyQualified) vd.getType());
8389
}
8490

85-
return DeclarationCheck.transformToVar(vd);
86-
// TODO implement to support cases like `var strs = List.<String>of();`
87-
/*J.VariableDeclarations finalVd = vd;
88-
return DeclarationCheck.<J.MethodInvocation>transformToVar(vd, it -> {
89-
// If left has generics but right has not, copy types parameters
90-
if (finalVd.getTypeExpression() instanceof J.ParameterizedType && !((J.ParameterizedType) finalVd.getTypeExpression()).getTypeParameters().isEmpty() && it.getTypeParameters() == null) {
91-
return it.withTypeParameters(((J.ParameterizedType) finalVd.getTypeExpression()).getPadding().getTypeParameters());
91+
// Make nested generic types explicit before converting to var
92+
J.VariableDeclarations finalVd = vd;
93+
return DeclarationCheck.transformToVar(vd, (J.MethodInvocation mi) -> makeNestedGenericsExplicit(mi, finalVd));
94+
}
95+
96+
/**
97+
* Makes nested generic types explicit by replacing diamond operators in constructor calls
98+
* with explicit type parameters based on the variable declaration type.
99+
*/
100+
private J.MethodInvocation makeNestedGenericsExplicit(J.MethodInvocation mi, J.VariableDeclarations vd) {
101+
// Extract type parameters from the variable declaration
102+
if (!(vd.getTypeExpression() instanceof J.ParameterizedType)) {
103+
return mi;
104+
}
105+
106+
J.ParameterizedType leftType = (J.ParameterizedType) vd.getTypeExpression();
107+
List<Expression> leftTypeParams = leftType.getTypeParameters();
108+
if (leftTypeParams == null || leftTypeParams.isEmpty()) {
109+
return mi;
110+
}
111+
112+
// Visit arguments and replace diamond operators with explicit type parameters
113+
return mi.withArguments(ListUtils.map(mi.getArguments(), arg -> {
114+
if (arg instanceof J.NewClass) {
115+
J.NewClass newClass = (J.NewClass) arg;
116+
List<JavaType> rightTypeParams = extractJavaTypes(newClass);
117+
// Check if using diamond operator (rightTypeParams is empty)
118+
if (rightTypeParams != null && rightTypeParams.isEmpty() && newClass.getClazz() instanceof J.ParameterizedType) {
119+
// Copy type parameters from left side to right side
120+
J.ParameterizedType rightType = (J.ParameterizedType) newClass.getClazz();
121+
return newClass.withClazz(
122+
rightType.withTypeParameters(leftTypeParams)
123+
);
124+
}
92125
}
93-
return it;
94-
});*/
126+
return arg;
127+
}));
128+
}
129+
130+
/**
131+
* Extract JavaTypes from a NewClass expression's type parameters.
132+
* Returns null if not a parameterized type, or an empty list for diamond operator.
133+
*/
134+
private @Nullable List<JavaType> extractJavaTypes(J.NewClass newClass) {
135+
TypeTree clazz = newClass.getClazz();
136+
if (clazz instanceof J.ParameterizedType) {
137+
List<Expression> typeParameters = ((J.ParameterizedType) clazz).getTypeParameters();
138+
List<JavaType> params = new ArrayList<>();
139+
if (typeParameters != null) {
140+
for (Expression curType : typeParameters) {
141+
JavaType type = curType.getType();
142+
if (type != null) {
143+
params.add(type);
144+
}
145+
}
146+
}
147+
return params;
148+
}
149+
return null;
95150
}
96151

97152
private static boolean allArgumentsEmpty(J.MethodInvocation invocation) {

src/test/java/org/openrewrite/java/migrate/lang/var/UseVarForGenericMethodInvocationsTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,85 @@ void m() {
259259
)
260260
);
261261
}
262+
263+
@Test
264+
void nestedGenericsSimple() {
265+
//language=java
266+
rewriteRun(
267+
version(
268+
java("""
269+
package com.example.app;
270+
271+
import java.util.ArrayList;
272+
import java.util.Collections;
273+
import java.util.List;
274+
275+
class A {
276+
private void dostuff() {
277+
List<String> strs = Collections.synchronizedList(new ArrayList<>());
278+
}
279+
}
280+
""", """
281+
package com.example.app;
282+
283+
import java.util.ArrayList;
284+
import java.util.Collections;
285+
import java.util.List;
286+
287+
class A {
288+
private void dostuff() {
289+
var strs = Collections.synchronizedList(new ArrayList<String>());
290+
}
291+
}
292+
"""),
293+
10
294+
)
295+
);
296+
}
297+
298+
@Test
299+
void nestedGenericsWithMethodReference() {
300+
//language=java
301+
rewriteRun(
302+
version(
303+
java("""
304+
package com.example.app;
305+
306+
import java.util.ArrayList;
307+
import java.util.Collections;
308+
import java.util.List;
309+
310+
class A {
311+
private void dostuff() {
312+
List<String> strs = Collections.synchronizedList(new ArrayList<>());
313+
strs.forEach(this::soString);
314+
}
315+
316+
private void soString(String s) {
317+
System.out.println(s);
318+
}
319+
}
320+
""", """
321+
package com.example.app;
322+
323+
import java.util.ArrayList;
324+
import java.util.Collections;
325+
import java.util.List;
326+
327+
class A {
328+
private void dostuff() {
329+
var strs = Collections.synchronizedList(new ArrayList<String>());
330+
strs.forEach(this::soString);
331+
}
332+
333+
private void soString(String s) {
334+
System.out.println(s);
335+
}
336+
}
337+
"""),
338+
10
339+
)
340+
);
341+
}
262342
}
263343
}

0 commit comments

Comments
 (0)