Skip to content

Commit f075120

Browse files
committed
Support WildcardType resolution in GenericTypeResolver
This commit adds support for WildcardType bounds resolution, commonly seen in Kotlin due to declaration-site variance, but also possible in Java even if less common. Closes gh-22313
1 parent b8f091e commit f075120

File tree

2 files changed

+101
-18
lines changed

2 files changed

+101
-18
lines changed

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

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* @author Rob Harrop
4040
* @author Sam Brannen
4141
* @author Phillip Webb
42+
* @author Sebastien Deleuze
4243
* @since 2.5.2
4344
*/
4445
public final class GenericTypeResolver {
@@ -166,29 +167,30 @@ public static Type resolveType(Type genericType, @Nullable Class<?> contextClass
166167
}
167168
else if (genericType instanceof ParameterizedType parameterizedType) {
168169
ResolvableType resolvedType = ResolvableType.forType(genericType);
169-
if (resolvedType.hasUnresolvableGenerics()) {
170-
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
171-
Type[] typeArguments = parameterizedType.getActualTypeArguments();
172-
ResolvableType contextType = ResolvableType.forClass(contextClass);
173-
for (int i = 0; i < typeArguments.length; i++) {
174-
Type typeArgument = typeArguments[i];
175-
if (typeArgument instanceof TypeVariable<?> typeVariable) {
176-
ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType);
177-
if (resolvedTypeArgument != ResolvableType.NONE) {
178-
generics[i] = resolvedTypeArgument.resolve();
179-
}
180-
else {
181-
generics[i] = ResolvableType.forType(typeArgument).resolve();
182-
}
170+
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
171+
Type[] typeArguments = parameterizedType.getActualTypeArguments();
172+
ResolvableType contextType = ResolvableType.forClass(contextClass);
173+
for (int i = 0; i < typeArguments.length; i++) {
174+
Type typeArgument = typeArguments[i];
175+
if (typeArgument instanceof TypeVariable<?> typeVariable) {
176+
ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType);
177+
if (resolvedTypeArgument != ResolvableType.NONE) {
178+
generics[i] = resolvedTypeArgument.resolve();
183179
}
184180
else {
185181
generics[i] = ResolvableType.forType(typeArgument).resolve();
186182
}
187183
}
188-
Class<?> rawClass = resolvedType.getRawClass();
189-
if (rawClass != null) {
190-
return ResolvableType.forClassWithGenerics(rawClass, generics).getType();
184+
else if (typeArgument instanceof WildcardType wildcardType) {
185+
generics[i] = resolveWildcard(wildcardType, contextType).resolve();
191186
}
187+
else {
188+
generics[i] = ResolvableType.forType(typeArgument).resolve();
189+
}
190+
}
191+
Class<?> rawClass = resolvedType.getRawClass();
192+
if (rawClass != null) {
193+
return ResolvableType.forClassWithGenerics(rawClass, generics).getType();
192194
}
193195
}
194196
}
@@ -224,6 +226,26 @@ private static ResolvableType resolveVariable(TypeVariable<?> typeVariable, Reso
224226
return ResolvableType.NONE;
225227
}
226228

229+
private static ResolvableType resolveWildcard(WildcardType wildcardType, ResolvableType contextType) {
230+
for (Type bound : wildcardType.getUpperBounds()) {
231+
if (bound instanceof TypeVariable<?> typeVariable) {
232+
ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType);
233+
if (resolvedTypeArgument != ResolvableType.NONE) {
234+
return resolvedTypeArgument;
235+
}
236+
}
237+
}
238+
for (Type bound : wildcardType.getLowerBounds()) {
239+
if (bound instanceof TypeVariable<?> typeVariable) {
240+
ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType);
241+
if (resolvedTypeArgument != ResolvableType.NONE) {
242+
return resolvedTypeArgument;
243+
}
244+
}
245+
}
246+
return ResolvableType.forType(wildcardType);
247+
}
248+
227249
/**
228250
* Resolve the specified generic type against the given TypeVariable map.
229251
* <p>Used by Spring Data.

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

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -37,6 +37,7 @@
3737
/**
3838
* @author Juergen Hoeller
3939
* @author Sam Brannen
40+
* @author Sebastien Deleuze
4041
*/
4142
@SuppressWarnings({"unchecked", "rawtypes"})
4243
class GenericTypeResolverTests {
@@ -185,6 +186,51 @@ public void resolveTransitiveTypeVariableWithDifferentName() {
185186
assertThat(resolved).isEqualTo(E.class);
186187
}
187188

189+
@Test
190+
void resolveWildcardTypeWithUpperBound() {
191+
Method method = findMethod(MySimpleSuperclassType.class, "upperBound", List.class);
192+
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
193+
ResolvableType resolvableType = ResolvableType.forType(resolved);
194+
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
195+
assertThat(resolvableType.resolveGenerics()).containsExactly(String.class);
196+
}
197+
198+
@Test
199+
void resolveWildcardTypeWithUpperBoundWithResolvedType() {
200+
Method method = findMethod(MySimpleSuperclassType.class, "upperBoundWithResolvedType", List.class);
201+
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
202+
ResolvableType resolvableType = ResolvableType.forType(resolved);
203+
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
204+
assertThat(resolvableType.resolveGenerics()).containsExactly(Integer.class);
205+
}
206+
207+
@Test
208+
void resolveWildcardTypeWithLowerBound() {
209+
Method method = findMethod(MySimpleSuperclassType.class, "lowerBound", List.class);
210+
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
211+
ResolvableType resolvableType = ResolvableType.forType(resolved);
212+
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
213+
assertThat(resolvableType.resolveGenerics()).containsExactly(String.class);
214+
}
215+
216+
@Test
217+
void resolveWildcardTypeWithLowerBoundWithResolvedType() {
218+
Method method = findMethod(MySimpleSuperclassType.class, "lowerBoundWithResolvedType", List.class);
219+
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
220+
ResolvableType resolvableType = ResolvableType.forType(resolved);
221+
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
222+
assertThat(resolvableType.resolveGenerics()).containsExactly(Integer.class);
223+
}
224+
225+
@Test
226+
void resolveWildcardTypeWithUnbounded() {
227+
Method method = findMethod(MySimpleSuperclassType.class, "unbounded", List.class);
228+
Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class);
229+
ResolvableType resolvableType = ResolvableType.forType(resolved);
230+
assertThat(resolvableType.hasUnresolvableGenerics()).isFalse();
231+
assertThat(resolvableType.resolveGenerics()).containsExactly(Object.class);
232+
}
233+
188234
public interface MyInterfaceType<T> {
189235
}
190236

@@ -195,6 +241,21 @@ public class MyCollectionInterfaceType implements MyInterfaceType<Collection<Str
195241
}
196242

197243
public abstract class MySuperclassType<T> {
244+
245+
public void upperBound(List<? extends T> list) {
246+
}
247+
248+
public void upperBoundWithResolvedType(List<? extends Integer> list) {
249+
}
250+
251+
public void lowerBound(List<? extends T> list) {
252+
}
253+
254+
public void lowerBoundWithResolvedType(List<? super Integer> list) {
255+
}
256+
257+
public void unbounded(List<?> list) {
258+
}
198259
}
199260

200261
public class MySimpleSuperclassType extends MySuperclassType<String> {

0 commit comments

Comments
 (0)