1616package org .openrewrite .java .migrate .lang .var ;
1717
1818import org .jspecify .annotations .Nullable ;
19- import org .openrewrite .*;
19+ import org .openrewrite .ExecutionContext ;
20+ import org .openrewrite .Preconditions ;
21+ import org .openrewrite .Recipe ;
22+ import org .openrewrite .TreeVisitor ;
23+ import org .openrewrite .internal .ListUtils ;
2024import org .openrewrite .java .JavaIsoVisitor ;
21- import org .openrewrite .java .JavaParser ;
22- import org .openrewrite .java .JavaTemplate ;
2325import org .openrewrite .java .search .UsesJavaVersion ;
2426import org .openrewrite .java .tree .*;
2527import org .openrewrite .marker .Markers ;
2628
2729import java .util .ArrayList ;
2830import java .util .List ;
31+ import java .util .Objects ;
2932
3033import static java .util .Collections .emptyList ;
34+ import static java .util .Collections .singleton ;
35+ import static org .openrewrite .Tree .randomId ;
3136
3237public class UseVarForGenericsConstructors extends Recipe {
3338 @ Override
@@ -50,11 +55,6 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
5055 }
5156
5257 static final class UseVarForGenericsConstructorsVisitor extends JavaIsoVisitor <ExecutionContext > {
53- private final JavaTemplate template = JavaTemplate .builder ("var #{} = #{any()}" )
54- .contextSensitive ()
55- .javaParser (JavaParser .fromJavaVersion ())
56- .build ();
57-
5858 @ Override
5959 public J .VariableDeclarations visitVariableDeclarations (J .VariableDeclarations vd , ExecutionContext ctx ) {
6060 vd = super .visitVariableDeclarations (vd , ctx );
@@ -64,68 +64,58 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations v
6464 return vd ;
6565 }
6666
67- // recipe specific
67+ // Recipe specific
6868 boolean isPrimitive = DeclarationCheck .isPrimitive (vd );
6969 boolean usesNoGenerics = !DeclarationCheck .useGenerics (vd );
7070 boolean usesTernary = DeclarationCheck .initializedByTernary (vd );
7171 if (isPrimitive || usesTernary || usesNoGenerics ) {
7272 return vd ;
7373 }
7474
75- //now we deal with generics
75+ // Now we deal with generics
7676 J .VariableDeclarations .NamedVariable variable = vd .getVariables ().get (0 );
77- List <JavaType > leftTypes = extractParameters (variable .getVariableType ());
78- List <JavaType > rightTypes = extractParameters (variable .getInitializer ());
77+ List <JavaType > leftTypes = extractTypeParameters (variable .getVariableType ());
78+ List <JavaType > rightTypes = extractTypeParameters (variable .getInitializer ());
7979 if (rightTypes == null || (leftTypes .isEmpty () && rightTypes .isEmpty ())) {
8080 return vd ;
8181 }
8282
83- // skip generics with type bounds, it's not yet implemented
84- for (JavaType type : leftTypes ) {
85- if (hasBounds ( type )) {
86- return vd ;
87- }
88- }
83+ // Java does not support declaration-site variance (see https://openjdk.org/jeps/300), things like `var x = new ArrayList<? extends Object>()` do not compile.
84+ // Therefore, skip variable declarations with generic wildcards.
8985 boolean genericHasBounds = anyTypeHasBounds (leftTypes );
9086 if (genericHasBounds ) {
9187 return vd ;
9288 }
9389
94- // mark imports for removal if unused
90+ // Mark imports for removal if unused
9591 if (vd .getType () instanceof JavaType .FullyQualified ) {
96- maybeRemoveImport ( (JavaType .FullyQualified ) vd .getType () );
92+ maybeRemoveImport ((JavaType .FullyQualified ) vd .getType ());
9793 }
9894
9995 return transformToVar (vd , leftTypes , rightTypes );
10096 }
10197
10298 private static Boolean anyTypeHasBounds (List <JavaType > leftTypes ) {
10399 for (JavaType type : leftTypes ) {
104- if (hasBounds ( type )) {
105- return true ;
100+ if (type instanceof JavaType .Parameterized ) {
101+ return anyTypeHasBounds (((JavaType .Parameterized ) type ).getTypeParameters ());
102+ }
103+ if (type instanceof JavaType .GenericTypeVariable ) {
104+ return !((JavaType .GenericTypeVariable ) type ).getBounds ().isEmpty ();
106105 }
107- }
108- return false ;
109- }
110-
111- private static boolean hasBounds (JavaType type ) {
112- if (type instanceof JavaType .Parameterized ) {
113- return anyTypeHasBounds (((JavaType .Parameterized ) type ).getTypeParameters ());
114- }
115- if (type instanceof JavaType .GenericTypeVariable ) {
116- return !((JavaType .GenericTypeVariable ) type ).getBounds ().isEmpty ();
117106 }
118107 return false ;
119108 }
120109
121110 /**
122111 * Tries to extract the generic parameters from the expression,
123- * if the Initializer is no new class or not of a parameterized type, returns null to signale "no info".
124- * if the initializer uses empty diamonds use an empty list to signale no type information
112+ * if the Initializer is no new class or not of a parameterized type, returns null to signal "no info".
113+ * if the initializer uses empty diamonds, use an empty list to signal no type information
114+ *
125115 * @param initializer to extract parameters from
126116 * @return null or list of type parameters in diamond
127117 */
128- private @ Nullable List <JavaType > extractParameters (@ Nullable Expression initializer ) {
118+ private @ Nullable List <JavaType > extractTypeParameters (@ Nullable Expression initializer ) {
129119 if (initializer instanceof J .NewClass ) {
130120 TypeTree clazz = ((J .NewClass ) initializer ).getClazz ();
131121 if (clazz instanceof J .ParameterizedType ) {
@@ -145,108 +135,36 @@ private static boolean hasBounds(JavaType type) {
145135 return null ;
146136 }
147137
148- /**
149- * Try to extract the parameters from the variables type.
150- * @param variable to extract from
151- * @return may be empty list of type parameters
152- */
153- private List <JavaType > extractParameters (JavaType .@ Nullable Variable variable ) {
138+ private List <JavaType > extractTypeParameters (JavaType .@ Nullable Variable variable ) {
154139 if (variable != null && variable .getType () instanceof JavaType .Parameterized ) {
155140 return ((JavaType .Parameterized ) variable .getType ()).getTypeParameters ();
156- } else {
157- return new ArrayList <>();
158141 }
142+ return new ArrayList <>();
159143 }
160144
161145 private J .VariableDeclarations transformToVar (J .VariableDeclarations vd , List <JavaType > leftTypes , List <JavaType > rightTypes ) {
162- Expression initializer = vd .getVariables ().get (0 ).getInitializer ();
163- String simpleName = vd .getVariables ().get (0 ).getSimpleName ();
164-
165-
166- // if left is defined but not right, copy types to initializer
167- if (rightTypes .isEmpty () && !leftTypes .isEmpty ()) {
168- // we need to switch type infos from left to right here
169- List <Expression > typeExpressions = new ArrayList <>();
170- for (JavaType curType : leftTypes ) {
171- typeExpressions .add (typeToExpression (curType ));
172- }
173-
174- J .ParameterizedType typedInitializerClazz = ((J .ParameterizedType ) ((J .NewClass ) initializer )
175- .getClazz ())
176- .withTypeParameters (typeExpressions );
177- initializer = ((J .NewClass ) initializer ).withClazz (typedInitializerClazz );
178- }
179-
180- J .VariableDeclarations result = template .<J .VariableDeclarations >apply (getCursor (), vd .getCoordinates ().replace (), simpleName , initializer )
181- .withPrefix (vd .getPrefix ());
182-
183- // apply modifiers like final
184- List <J .Modifier > modifiers = vd .getModifiers ();
185- boolean hasModifiers = !modifiers .isEmpty ();
186- if (hasModifiers ) {
187- result = result .withModifiers (modifiers );
188- }
189-
190- // apply prefix to type expression
191- TypeTree resultingTypeExpression = result .getTypeExpression ();
192- boolean resultHasTypeExpression = resultingTypeExpression != null ;
193- if (resultHasTypeExpression ) {
194- result = result .withTypeExpression (resultingTypeExpression .withPrefix (vd .getTypeExpression ().getPrefix ()));
195- }
196-
197- return result ;
198- }
199-
200- /**
201- * recursively map a JavaType to an Expression with same semantics
202- * @param type to map
203- * @return semantically equal Expression
204- */
205- private static Expression typeToExpression (JavaType type ) {
206- if (type instanceof JavaType .Primitive ) {
207- JavaType .Primitive primitiveType = JavaType .Primitive .fromKeyword (((JavaType .Primitive ) type ).getKeyword ());
208- return new J .Primitive (Tree .randomId (), Space .EMPTY , Markers .EMPTY , primitiveType );
209- }
210- if (type instanceof JavaType .Class ) {
211- String className = ((JavaType .Class ) type ).getClassName ();
212- return new J .Identifier (Tree .randomId (), Space .EMPTY , Markers .EMPTY , emptyList (), className , type , null );
213- }
214- if (type instanceof JavaType .Array ) {
215- TypeTree elemType = (TypeTree ) typeToExpression (((JavaType .Array ) type ).getElemType ());
216- return new J .ArrayType (Tree .randomId (), Space .EMPTY , Markers .EMPTY , elemType , null , JLeftPadded .build (Space .EMPTY ), type );
217- }
218- if (type instanceof JavaType .GenericTypeVariable ) {
219- String variableName = ((JavaType .GenericTypeVariable ) type ).getName ();
220- J .Identifier identifier = new J .Identifier (Tree .randomId (), Space .EMPTY , Markers .EMPTY , emptyList (), variableName , type , null );
221-
222- List <JavaType > bounds1 = ((JavaType .GenericTypeVariable ) type ).getBounds ();
223- if (bounds1 .isEmpty ()) {
224- return identifier ;
225- } else {
226- /*
227- List<JRightPadded<TypeTree>> bounds = bounds1.stream()
228- .map(b -> new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, , null, null))
229- .map(JRightPadded::build)
230- .collect(Collectors.toList());
231-
232- return new J.TypeParameter(Tree.randomId(), Space.EMPTY, Markers.EMPTY, new ArrayList<>(), identifier, JContainer.build(bounds));
233- */
234- throw new IllegalStateException ("Generic type variables with bound are not supported, yet." );
235- }
236- }
237- if (type instanceof JavaType .Parameterized ) { // recursively parse
238- List <JavaType > typeParameters = ((JavaType .Parameterized ) type ).getTypeParameters ();
239-
240- List <JRightPadded <Expression >> typeParamsExpression = new ArrayList <>(typeParameters .size ());
241- for (JavaType curType : typeParameters ) {
242- typeParamsExpression .add (JRightPadded .build (typeToExpression (curType )));
243- }
244-
245- NameTree clazz = new J .Identifier (Tree .randomId (), Space .EMPTY , Markers .EMPTY , emptyList (), ((JavaType .Parameterized ) type ).getClassName (), null , null );
246- return new J .ParameterizedType (Tree .randomId (), Space .EMPTY , Markers .EMPTY , clazz , JContainer .build (typeParamsExpression ), type );
247- }
248-
249- throw new IllegalArgumentException (String .format ("Unable to parse expression from JavaType %s" , type ));
146+ J .NewClass initializer = Objects .requireNonNull ((J .NewClass ) vd .getVariables ().get (0 ).getInitializer ());
147+
148+ // If left is defined but right is not, copy types from typeExpression to initializer
149+ if (rightTypes .isEmpty () && !leftTypes .isEmpty () && vd .getTypeExpression () instanceof J .ParameterizedType && initializer .getClazz () instanceof J .ParameterizedType ) {
150+ J .ParameterizedType typedInitializerClazz = ((J .ParameterizedType ) initializer .getClazz ())
151+ .withTypeParameters (((J .ParameterizedType ) vd .getTypeExpression ()).getTypeParameters ());
152+ initializer = initializer .withClazz (typedInitializerClazz );
153+ }
154+
155+ // Replace actual type by `var` keyword and replace the first variable's name, initializer and type
156+ J .NewClass finalInitializer = initializer ;
157+ List <J .VariableDeclarations .NamedVariable > variables = ListUtils .mapFirst (vd .getVariables (), it -> {
158+ JavaType .Variable variableType = it .getVariableType () == null ? null : it .getVariableType ().withOwner (null );
159+ return it
160+ .withName (it .getName ().withType (finalInitializer .getType ()).withFieldType (variableType ))
161+ .withInitializer (finalInitializer )
162+ .withVariableType (variableType );
163+ });
164+ J .Identifier typeExpression = new J .Identifier (randomId (), vd .getTypeExpression ().getPrefix (),
165+ Markers .build (singleton (JavaVarKeyword .build ())), emptyList (), "var" , initializer .getType (), null );
166+
167+ return vd .withVariables (variables ).withTypeExpression (typeExpression );
250168 }
251169 }
252170}
0 commit comments