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
This commit is contained in:
		
							parent
							
								
									b8f091e2f6
								
							
						
					
					
						commit
						f075120675
					
				|  | @ -39,6 +39,7 @@ import org.springframework.util.ConcurrentReferenceHashMap; | |||
|  * @author Rob Harrop | ||||
|  * @author Sam Brannen | ||||
|  * @author Phillip Webb | ||||
|  * @author Sebastien Deleuze | ||||
|  * @since 2.5.2 | ||||
|  */ | ||||
| public final class GenericTypeResolver { | ||||
|  | @ -166,7 +167,6 @@ public final class GenericTypeResolver { | |||
| 			} | ||||
| 			else if (genericType instanceof ParameterizedType parameterizedType) { | ||||
| 				ResolvableType resolvedType = ResolvableType.forType(genericType); | ||||
| 				if (resolvedType.hasUnresolvableGenerics()) { | ||||
| 				Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length]; | ||||
| 				Type[] typeArguments = parameterizedType.getActualTypeArguments(); | ||||
| 				ResolvableType contextType = ResolvableType.forClass(contextClass); | ||||
|  | @ -181,6 +181,9 @@ public final class GenericTypeResolver { | |||
| 							generics[i] = ResolvableType.forType(typeArgument).resolve(); | ||||
| 						} | ||||
| 					} | ||||
| 					else if (typeArgument instanceof WildcardType wildcardType) { | ||||
| 						generics[i] = resolveWildcard(wildcardType, contextType).resolve(); | ||||
| 					} | ||||
| 					else { | ||||
| 						generics[i] = ResolvableType.forType(typeArgument).resolve(); | ||||
| 					} | ||||
|  | @ -191,7 +194,6 @@ public final class GenericTypeResolver { | |||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		} | ||||
| 		return genericType; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -224,6 +226,26 @@ public final class GenericTypeResolver { | |||
| 		return ResolvableType.NONE; | ||||
| 	} | ||||
| 
 | ||||
| 	private static ResolvableType resolveWildcard(WildcardType wildcardType, ResolvableType contextType) { | ||||
| 		for (Type bound : wildcardType.getUpperBounds()) { | ||||
| 			if (bound instanceof TypeVariable<?> typeVariable) { | ||||
| 				ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); | ||||
| 				if (resolvedTypeArgument != ResolvableType.NONE) { | ||||
| 					return resolvedTypeArgument; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		for (Type bound : wildcardType.getLowerBounds()) { | ||||
| 			if (bound instanceof TypeVariable<?> typeVariable) { | ||||
| 				ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); | ||||
| 				if (resolvedTypeArgument != ResolvableType.NONE) { | ||||
| 					return resolvedTypeArgument; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return ResolvableType.forType(wildcardType); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Resolve the specified generic type against the given TypeVariable map. | ||||
| 	 * <p>Used by Spring Data. | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| /* | ||||
|  * Copyright 2002-2022 the original author or authors. | ||||
|  * Copyright 2002-2023 the original author or authors. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  | @ -37,6 +37,7 @@ import static org.springframework.util.ReflectionUtils.findMethod; | |||
| /** | ||||
|  * @author Juergen Hoeller | ||||
|  * @author Sam Brannen | ||||
|  * @author Sebastien Deleuze | ||||
|  */ | ||||
| @SuppressWarnings({"unchecked", "rawtypes"}) | ||||
| class GenericTypeResolverTests { | ||||
|  | @ -185,6 +186,51 @@ class GenericTypeResolverTests { | |||
| 		assertThat(resolved).isEqualTo(E.class); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void resolveWildcardTypeWithUpperBound() { | ||||
| 		Method method = findMethod(MySimpleSuperclassType.class, "upperBound", List.class); | ||||
| 		Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class); | ||||
| 		ResolvableType resolvableType = ResolvableType.forType(resolved); | ||||
| 		assertThat(resolvableType.hasUnresolvableGenerics()).isFalse(); | ||||
| 		assertThat(resolvableType.resolveGenerics()).containsExactly(String.class); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void resolveWildcardTypeWithUpperBoundWithResolvedType() { | ||||
| 		Method method = findMethod(MySimpleSuperclassType.class, "upperBoundWithResolvedType", List.class); | ||||
| 		Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class); | ||||
| 		ResolvableType resolvableType = ResolvableType.forType(resolved); | ||||
| 		assertThat(resolvableType.hasUnresolvableGenerics()).isFalse(); | ||||
| 		assertThat(resolvableType.resolveGenerics()).containsExactly(Integer.class); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void resolveWildcardTypeWithLowerBound() { | ||||
| 		Method method = findMethod(MySimpleSuperclassType.class, "lowerBound", List.class); | ||||
| 		Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class); | ||||
| 		ResolvableType resolvableType = ResolvableType.forType(resolved); | ||||
| 		assertThat(resolvableType.hasUnresolvableGenerics()).isFalse(); | ||||
| 		assertThat(resolvableType.resolveGenerics()).containsExactly(String.class); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void resolveWildcardTypeWithLowerBoundWithResolvedType() { | ||||
| 		Method method = findMethod(MySimpleSuperclassType.class, "lowerBoundWithResolvedType", List.class); | ||||
| 		Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class); | ||||
| 		ResolvableType resolvableType = ResolvableType.forType(resolved); | ||||
| 		assertThat(resolvableType.hasUnresolvableGenerics()).isFalse(); | ||||
| 		assertThat(resolvableType.resolveGenerics()).containsExactly(Integer.class); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	void resolveWildcardTypeWithUnbounded() { | ||||
| 		Method method = findMethod(MySimpleSuperclassType.class, "unbounded", List.class); | ||||
| 		Type resolved = resolveType(method.getGenericParameterTypes()[0], MySimpleSuperclassType.class); | ||||
| 		ResolvableType resolvableType = ResolvableType.forType(resolved); | ||||
| 		assertThat(resolvableType.hasUnresolvableGenerics()).isFalse(); | ||||
| 		assertThat(resolvableType.resolveGenerics()).containsExactly(Object.class); | ||||
| 	} | ||||
| 
 | ||||
| 	public interface MyInterfaceType<T> { | ||||
| 	} | ||||
| 
 | ||||
|  | @ -195,6 +241,21 @@ class GenericTypeResolverTests { | |||
| 	} | ||||
| 
 | ||||
| 	public abstract class MySuperclassType<T> { | ||||
| 
 | ||||
| 		public void upperBound(List<? extends T> list) { | ||||
| 		} | ||||
| 
 | ||||
| 		public void upperBoundWithResolvedType(List<? extends Integer> list) { | ||||
| 		} | ||||
| 
 | ||||
| 		public void lowerBound(List<? extends T> list) { | ||||
| 		} | ||||
| 
 | ||||
| 		public void lowerBoundWithResolvedType(List<? super Integer> list) { | ||||
| 		} | ||||
| 
 | ||||
| 		public void unbounded(List<?> list) { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public class MySimpleSuperclassType extends MySuperclassType<String> { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue