From 9130ded96f4b6ed19751bffac6baec526cdab879 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 22 Jun 2026 22:09:52 +0200 Subject: [PATCH] Resolve against type variable from same declaration if possible Closes gh-36890 --- .../springframework/core/ResolvableType.java | 20 ++++++++++++------- .../core/GenericTypeResolverTests.java | 20 ++++++++++++++++++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index e4cc5bf4258..3d69e9fd92c 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -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]; } } diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index f491e7ea1b1..150259ce79e 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -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 { + } + + interface Create { + default O create(I body) { + return null; + } + } + + static class Controller implements Search, Create { + } + }