Skip to content

Commit

Permalink
Support WildcardType resolution in GenericTypeResolver
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sdeleuze committed Jun 30, 2023
1 parent b8f091e commit f075120
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
* @author Rob Harrop
* @author Sam Brannen
* @author Phillip Webb
* @author Sebastien Deleuze
* @since 2.5.2
*/
public final class GenericTypeResolver {
Expand Down Expand Up @@ -166,29 +167,30 @@ public static Type resolveType(Type genericType, @Nullable Class<?> contextClass
}
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);
for (int i = 0; i < typeArguments.length; i++) {
Type typeArgument = typeArguments[i];
if (typeArgument instanceof TypeVariable<?> typeVariable) {
ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType);
if (resolvedTypeArgument != ResolvableType.NONE) {
generics[i] = resolvedTypeArgument.resolve();
}
else {
generics[i] = ResolvableType.forType(typeArgument).resolve();
}
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
Type[] typeArguments = parameterizedType.getActualTypeArguments();
ResolvableType contextType = ResolvableType.forClass(contextClass);
for (int i = 0; i < typeArguments.length; i++) {
Type typeArgument = typeArguments[i];
if (typeArgument instanceof TypeVariable<?> typeVariable) {
ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType);
if (resolvedTypeArgument != ResolvableType.NONE) {
generics[i] = resolvedTypeArgument.resolve();
}
else {
generics[i] = ResolvableType.forType(typeArgument).resolve();
}
}
Class<?> rawClass = resolvedType.getRawClass();
if (rawClass != null) {
return ResolvableType.forClassWithGenerics(rawClass, generics).getType();
else if (typeArgument instanceof WildcardType wildcardType) {
generics[i] = resolveWildcard(wildcardType, contextType).resolve();
}
else {
generics[i] = ResolvableType.forType(typeArgument).resolve();
}
}
Class<?> rawClass = resolvedType.getRawClass();
if (rawClass != null) {
return ResolvableType.forClassWithGenerics(rawClass, generics).getType();
}
}
}
Expand Down Expand Up @@ -224,6 +226,26 @@ private static ResolvableType resolveVariable(TypeVariable<?> typeVariable, Reso
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -37,6 +37,7 @@
/**
* @author Juergen Hoeller
* @author Sam Brannen
* @author Sebastien Deleuze
*/
@SuppressWarnings({"unchecked", "rawtypes"})
class GenericTypeResolverTests {
Expand Down Expand Up @@ -185,6 +186,51 @@ public void resolveTransitiveTypeVariableWithDifferentName() {
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> {
}

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

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> {
Expand Down

0 comments on commit f075120

Please sign in to comment.