Skip to content

Commit 033f84e

Browse files
Replace JavaTemplate by a var identifier element for UseVarForGenericsConstructors recipe (#789)
* Replace JavaTemplate by a `var` identifier element * Polish * Polish * Polish * Polish * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Polish * Polish * Polish * Don't change anything if java < 10 --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 38fd948 commit 033f84e

File tree

2 files changed

+190
-291
lines changed

2 files changed

+190
-291
lines changed

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

Lines changed: 49 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,23 @@
1616
package org.openrewrite.java.migrate.lang.var;
1717

1818
import 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;
2024
import org.openrewrite.java.JavaIsoVisitor;
21-
import org.openrewrite.java.JavaParser;
22-
import org.openrewrite.java.JavaTemplate;
2325
import org.openrewrite.java.search.UsesJavaVersion;
2426
import org.openrewrite.java.tree.*;
2527
import org.openrewrite.marker.Markers;
2628

2729
import java.util.ArrayList;
2830
import java.util.List;
31+
import java.util.Objects;
2932

3033
import static java.util.Collections.emptyList;
34+
import static java.util.Collections.singleton;
35+
import static org.openrewrite.Tree.randomId;
3136

3237
public 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

Comments
 (0)