Skip to content

Commit 5cf8978

Browse files
committed
Restore proper code generation for types with nested generics
This commit aligns code generation to recent improvement in the core container regarding type detection. Now that nested types are properly resolved, our code generation that uses hasResolvableGenerics() is taking the wrong decision if only a nested type has an unresolved generics. Previously, this was hidden by the fact that the core container would not resolve them recursively. A new hasResolvableGenerics() method allows to verify that at least one direct generic type is resolved. This restore our intent of checking at the first level only and let recursive invocations figure out if they have to write the raw type or the type with generics. Closes gh-33069
1 parent 1047e1f commit 5cf8978

File tree

5 files changed

+65
-8
lines changed

5 files changed

+65
-8
lines changed

spring-core/src/main/java/org/springframework/aot/generate/ValueCodeGeneratorDelegates.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ private static CodeBlock generateCode(ResolvableType resolvableType, boolean all
331331
return CodeBlock.of("$T.NONE", ResolvableType.class);
332332
}
333333
Class<?> type = ClassUtils.getUserClass(resolvableType.toClass());
334-
if (resolvableType.hasGenerics() && !resolvableType.hasUnresolvableGenerics()) {
334+
if (resolvableType.hasGenerics() && resolvableType.hasResolvableGenerics()) {
335335
return generateCodeWithGenerics(resolvableType, type);
336336
}
337337
if (allowClassResult) {

spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ private static Class<?> getSingleGeneric(ResolvableType resolvableType) {
138138
@Nullable
139139
public static Class<?>[] resolveTypeArguments(Class<?> clazz, Class<?> genericType) {
140140
ResolvableType type = ResolvableType.forClass(clazz).as(genericType);
141-
if (!type.hasGenerics() || type.isEntirelyUnresolvable()) {
141+
if (!type.hasGenerics() || !type.hasResolvableGenerics()) {
142142
return null;
143143
}
144144
return type.resolveGenerics(Object.class);

spring-core/src/main/java/org/springframework/core/ResolvableType.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -568,20 +568,23 @@ public boolean hasGenerics() {
568568
}
569569

570570
/**
571-
* Return {@code true} if this type contains unresolvable generics only,
572-
* that is, no substitute for any of its declared type variables.
571+
* Return {@code true} if this type contains at least a generic type
572+
* that is resolved. In other words, this returns {@code false} if
573+
* the type contains unresolvable generics only, that is, no substitute
574+
* for any of its declared type variables.
575+
* @since 6.2
573576
*/
574-
boolean isEntirelyUnresolvable() {
577+
public boolean hasResolvableGenerics() {
575578
if (this == NONE) {
576579
return false;
577580
}
578581
ResolvableType[] generics = getGenerics();
579582
for (ResolvableType generic : generics) {
580583
if (!generic.isUnresolvableTypeVariable() && !generic.isWildcardWithoutBounds()) {
581-
return false;
584+
return true;
582585
}
583586
}
584-
return true;
587+
return false;
585588
}
586589

587590
/**

spring-core/src/test/java/org/springframework/aot/generate/ValueCodeGeneratorTests.java

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -290,6 +290,34 @@ void generateWhenNestedGenericResolvableType() {
290290
+ "ResolvableType.forClassWithGenerics(List.class, String.class))");
291291
}
292292

293+
@Test
294+
void generateWhenUnresolvedGenericType() throws NoSuchFieldException {
295+
ResolvableType resolvableType = ResolvableType
296+
.forField(SampleTypes.class.getField("genericList"));
297+
assertThat(resolve(generateCode(resolvableType)))
298+
.hasImport(ResolvableType.class, List.class)
299+
.hasValueCode("ResolvableType.forClass(List.class)");
300+
}
301+
302+
@Test
303+
void generateWhenUnresolvedNestedGenericType() throws NoSuchFieldException {
304+
ResolvableType resolvableType = ResolvableType
305+
.forField(SampleTypes.class.getField("mapWithNestedGenericInValueType"));
306+
assertThat(resolve(generateCode(resolvableType)))
307+
.hasImport(ResolvableType.class, List.class)
308+
.hasValueCode("""
309+
ResolvableType.forClassWithGenerics(Map.class, ResolvableType.forClass(String.class), \
310+
ResolvableType.forClass(List.class))""");
311+
}
312+
313+
static class SampleTypes<A> {
314+
315+
public List<A> genericList;
316+
317+
public Map<String, List<A>> mapWithNestedGenericInValueType;
318+
319+
}
320+
293321
}
294322

295323
@Nested

spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java

+26
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,30 @@ void narrow() throws Exception {
12891289
assertThat(narrow.getGeneric().resolve()).isEqualTo(String.class);
12901290
}
12911291

1292+
@Test
1293+
void hasResolvableGenerics() throws Exception {
1294+
ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList"));
1295+
assertThat(type.hasResolvableGenerics()).isTrue();
1296+
}
1297+
1298+
@Test
1299+
void hasResolvableGenericsWithSingleBoundedWildcard() throws Exception {
1300+
ResolvableType type = ResolvableType.forField(Fields.class.getField("wildcardType"));
1301+
assertThat(type.hasResolvableGenerics()).isTrue();
1302+
}
1303+
1304+
@Test
1305+
void hasResolvableGenericsWithSingleParameterizedType() throws Exception {
1306+
ResolvableType type = ResolvableType.forField(Fields.class.getField("parameterizedType"));
1307+
assertThat(type.hasResolvableGenerics()).isFalse();
1308+
}
1309+
1310+
@Test
1311+
void hasResolvableGenericsWithSingleWildcard() throws Exception {
1312+
ResolvableType type = ResolvableType.forField(Fields.class.getField("anyListElement"));
1313+
assertThat(type.hasResolvableGenerics()).isFalse();
1314+
}
1315+
12921316
@Test
12931317
void hasUnresolvableGenerics() throws Exception {
12941318
ResolvableType type = ResolvableType.forField(Fields.class.getField("stringList"));
@@ -1466,6 +1490,8 @@ static class Fields<T> {
14661490

14671491
public List<String>[][][] genericMultiArrayType;
14681492

1493+
public List<?> anyListElement;
1494+
14691495
public List<? extends Number> wildcardType;
14701496

14711497
public List<? super Number> wildcardSuperType = new ArrayList<Object>();

0 commit comments

Comments
 (0)