Resolve against type variable from same declaration if possible

Closes gh-36890
This commit is contained in:
Juergen Hoeller
2026-06-22 22:09:52 +02:00
parent 0dc2d03093
commit 9130ded96f
2 changed files with 32 additions and 8 deletions
@@ -943,8 +943,9 @@ public class ResolvableType implements Serializable {
}
private @Nullable ResolvableType resolveVariable(TypeVariable<?> variable) {
TypeVariable<?> variableToCompare = SerializableTypeWrapper.unwrap(variable);
if (this.type instanceof TypeVariable) {
return resolveType().resolveVariable(variable);
return resolveType().resolveVariable(variableToCompare);
}
if (this.type instanceof ParameterizedType parameterizedType) {
Class<?> resolved = resolve();
@@ -954,23 +955,29 @@ public class ResolvableType implements Serializable {
TypeVariable<?>[] variables = resolved.getTypeParameters();
Type[] typeArguments = parameterizedType.getActualTypeArguments();
for (int i = 0; i < variables.length; i++) {
if (ObjectUtils.nullSafeEquals(variables[i].getName(), variable.getName())) {
if (ObjectUtils.nullSafeEquals(variables[i], variableToCompare)) {
return forType(typeArguments[i], this.variableResolver);
}
}
Type ownerType = parameterizedType.getOwnerType();
if (ownerType != null) {
return forType(ownerType, this.variableResolver).resolveVariable(variable);
return forType(ownerType, this.variableResolver).resolveVariable(variableToCompare);
}
// Fallback: comparison by variable name, independent of generic declaration context.
for (int i = 0; i < variables.length; i++) {
if (ObjectUtils.nullSafeEquals(variables[i].getName(), variableToCompare.getName())) {
return forType(typeArguments[i], this.variableResolver);
}
}
}
if (this.type instanceof WildcardType) {
ResolvableType resolved = resolveType().resolveVariable(variable);
ResolvableType resolved = resolveType().resolveVariable(variableToCompare);
if (resolved != null) {
return resolved;
}
}
if (this.variableResolver != null) {
return this.variableResolver.resolveVariable(variable);
return this.variableResolver.resolveVariable(variableToCompare);
}
return null;
}
@@ -1616,8 +1623,7 @@ public class ResolvableType implements Serializable {
public @Nullable ResolvableType resolveVariable(TypeVariable<?> variable) {
TypeVariable<?> variableToCompare = SerializableTypeWrapper.unwrap(variable);
for (int i = 0; i < this.variables.length; i++) {
TypeVariable<?> resolvedVariable = SerializableTypeWrapper.unwrap(this.variables[i]);
if (ObjectUtils.nullSafeEquals(resolvedVariable, variableToCompare)) {
if (ObjectUtils.nullSafeEquals(this.variables[i], variableToCompare)) {
return this.generics[i];
}
}
@@ -251,7 +251,7 @@ class GenericTypeResolverTests {
assertThat(resolvedType).isEqualTo(InheritsDefaultMethod.ConcreteType.class);
}
@Test
@Test // gh-36480
void resolveTypeFromNestedParameterizedType() {
Type resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyCollectionInterfaceType.class);
assertThat(resolvedType).isEqualTo(method(MyCollectionInterfaceType.class, "get").getGenericReturnType());
@@ -260,6 +260,12 @@ class GenericTypeResolverTests {
assertThat(resolvedType).isEqualTo(method(MyOptionalInterfaceType.class, "get").getGenericReturnType());
}
@Test // gh-36890
void resolveTypeAgainstSameNamedVariables() {
Type resolvedType = resolveType(method(Create.class, "create", Object.class).getGenericParameterTypes()[0], Controller.class);
assertThat(resolvedType).isEqualTo(Long.class);
}
private static Method method(Class<?> target, String methodName, Class<?>... parameterTypes) {
Method method = findMethod(target, methodName, parameterTypes);
assertThat(method).describedAs(target.getName() + "#" + methodName).isNotNull();
@@ -504,4 +510,16 @@ class GenericTypeResolverTests {
}
}
interface Search<I, O> {
}
interface Create<I, O> {
default O create(I body) {
return null;
}
}
static class Controller implements Search<String, Long>, Create<Long, Long> {
}
}