From 09788ea1dde5cdd9e6d3bdad30028327b57bfbc7 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 16 Mar 2026 14:54:04 +0700 Subject: [PATCH 1/7] Add test cases Signed-off-by: anaconda875 --- .../support/RestClientAdapterTests.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java index 95a8d6b8ec4f..0ec36e5cb429 100644 --- a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java @@ -25,6 +25,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.LinkedHashSet; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; @@ -103,6 +104,12 @@ void shutdown() { @interface ParameterizedAdapterTest { } + public static Stream wildcardCases() { + return Stream.of( + (Function>) BaseClient::getListWildcardUpperBound1, + (Function>) BaseClient::getListWildcardUpperBound2); + } + public static Stream arguments() throws IOException { return Stream.of( createArgsForAdapter((url, or) -> { @@ -216,6 +223,15 @@ void getEntityWithGenericReturnType() { assertThat(entity.getBody().name()).isEqualTo("Karl"); } + @ParameterizedTest + @MethodSource("wildcardCases") + void getWildcardReturnType(Function> invocation) { + PersonClient client = initService(PersonClient.class); + prepareResponse(r -> r.setHeader("Content-Type", "application/json").body("[{\"name\":\"Karl\"}]")); + List list = invocation.apply(client); + assertThat(list.get(0).name()).isEqualTo("Karl"); + } + @ParameterizedAdapterTest void getWithUriBuilderFactory(MockWebServer server, Service service) throws InterruptedException { prepareResponse(builder -> @@ -467,6 +483,12 @@ private interface BaseClient { @GetExchange T getBody(); + @GetExchange + List getListWildcardUpperBound1(); + + @GetExchange + List getListWildcardUpperBound2(); + @GetExchange ResponseEntity getEntity(); } From fed2e793eb7c2180d5acc0c99f9cf5ea626f6a73 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 16 Mar 2026 15:41:30 +0700 Subject: [PATCH 2/7] Fix generic with WildcardType return type support in HttpServiceMethod Signed-off-by: anaconda875 --- .../core/GenericTypeResolver.java | 42 ++++-- .../springframework/core/ResolvableType.java | 124 +++++++++++++++++- .../core/GenericTypeResolverTests.java | 28 ++++ .../core/ResolvableTypeTests.java | 32 ++++- 4 files changed, 211 insertions(+), 15 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 083964732733..1cd981de6c38 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -154,11 +154,8 @@ public static Class resolveReturnType(Method method, Class clazz) { public static Type resolveType(Type genericType, @Nullable Class contextClass) { if (contextClass != null) { if (genericType instanceof TypeVariable typeVariable) { - ResolvableType resolvedTypeVariable = resolveVariable( + ResolvableType resolvedTypeVariable = resolveVariableConsiderBound( typeVariable, ResolvableType.forClass(contextClass)); - if (resolvedTypeVariable == ResolvableType.NONE) { - resolvedTypeVariable = ResolvableType.forVariableBounds(typeVariable); - } if (resolvedTypeVariable != ResolvableType.NONE) { Type type = resolvedTypeVariable.getType(); if (type instanceof ParameterizedType) { @@ -179,10 +176,8 @@ else if (genericType instanceof ParameterizedType parameterizedType) { 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) { - resolvedTypeArgument = ResolvableType.forVariableBounds(typeVariable); - } + ResolvableType resolvedTypeArgument = resolveVariableConsiderBound( + typeVariable, contextType); if (resolvedTypeArgument != ResolvableType.NONE) { generics[i] = resolvedTypeArgument; } @@ -190,7 +185,7 @@ else if (genericType instanceof ParameterizedType parameterizedType) { generics[i] = ResolvableType.forType(typeArgument); } } - else if (typeArgument instanceof ParameterizedType) { + else if (typeArgument instanceof ParameterizedType || typeArgument instanceof WildcardType) { generics[i] = ResolvableType.forType(resolveType(typeArgument, contextClass)); } else { @@ -203,10 +198,39 @@ else if (typeArgument instanceof ParameterizedType) { } } } + else if (genericType instanceof WildcardType wildcardType) { + Type[] originalLowerBound = wildcardType.getLowerBounds(); + Type[] originalUpperBound = wildcardType.getUpperBounds(); + + if (originalLowerBound.length == 1) { + Type lowerBound = resolveType(originalLowerBound[0], contextClass); + if (lowerBound != originalLowerBound[0]) { + return ResolvableType.forWildCardTypeWithLowerBound( + wildcardType, ResolvableType.forType(lowerBound)) + .getType(); + } + } else if (originalUpperBound.length == 1) { + Type upperBound = resolveType(originalUpperBound[0], contextClass); + if (upperBound != originalUpperBound[0]) { + return ResolvableType.forWildCardTypeWithUpperBound( + wildcardType, ResolvableType.forType(upperBound)) + .getType(); + } + } + return wildcardType; + } } return genericType; } + private static ResolvableType resolveVariableConsiderBound(TypeVariable typeVariable, ResolvableType contextType) { + ResolvableType resolvedTypeArgument = resolveVariable(typeVariable, contextType); + if (resolvedTypeArgument == ResolvableType.NONE) { + resolvedTypeArgument = ResolvableType.forVariableBounds(typeVariable); + } + return resolvedTypeArgument; + } + private static ResolvableType resolveVariable(TypeVariable typeVariable, ResolvableType contextType) { ResolvableType resolvedType; if (contextType.hasGenerics()) { 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 af01421dc27f..8b51ecb0b2ce 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -98,6 +98,8 @@ public class ResolvableType implements Serializable { private static final ConcurrentReferenceHashMap cache = new ConcurrentReferenceHashMap<>(256); + private static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; + /** * The underlying Java type being managed. @@ -616,7 +618,8 @@ private boolean determineUnresolvableGenerics(@Nullable Set alreadySeen) { ResolvableType[] generics = getGenerics(); for (ResolvableType generic : generics) { - if (generic.isUnresolvableTypeVariable() || generic.isWildcardWithoutBounds() || + if (generic.isUnresolvableTypeVariable() || + generic.isUnresolvableWildcard(currentTypeSeen(alreadySeen)) || generic.hasUnresolvableGenerics(currentTypeSeen(alreadySeen))) { return true; } @@ -676,14 +679,32 @@ private boolean isWildcardWithoutBounds() { if (this.type instanceof WildcardType wildcardType) { if (wildcardType.getLowerBounds().length == 0) { Type[] upperBounds = wildcardType.getUpperBounds(); - if (upperBounds.length == 0 || (upperBounds.length == 1 && Object.class == upperBounds[0])) { - return true; - } + return upperBounds.length == 0 || (upperBounds.length == 1 && (Object.class == upperBounds[0])); } } return false; } + /** + * Determine whether the underlying type represents a wildcard + * has unresolvable upper bound or lower bound, or simply without bound + */ + private boolean isUnresolvableWildcard(Set alreadySeen) { + if (this.type instanceof WildcardType wildcardType) { + Type[] lowerBounds = wildcardType.getLowerBounds(); + if (lowerBounds.length == 1) { + ResolvableType lowerResolvable = ResolvableType.forType(lowerBounds[0], this.variableResolver); + return lowerResolvable.isUnresolvableTypeVariable() || lowerResolvable.determineUnresolvableGenerics(alreadySeen); + } + Type[] upperBounds = wildcardType.getUpperBounds(); + if (upperBounds.length == 1 && upperBounds[0] != Object.class) { + ResolvableType upperResolvable = ResolvableType.forType(upperBounds[0], this.variableResolver); + return upperResolvable.isUnresolvableTypeVariable() || upperResolvable.determineUnresolvableGenerics(alreadySeen); + } + } + return isWildcardWithoutBounds(); + } + /** * Return a {@code ResolvableType} for the specified nesting level. *

See {@link #getNested(int, Map)} for details. @@ -1185,6 +1206,51 @@ public static ResolvableType forClassWithGenerics(Class clazz, @Nullable Reso (generics != null ? new TypeVariablesVariableResolver(variables, generics) : null)); } + /** + * Return a {@code ResolvableType} for the specified {@link WildcardType} with pre-declared upper bound. + * @param wildcardType the WildcardType to introspect + * @param upperBound the upper bound of the wildcardType + * @return a {@code ResolvableType} for the specific wildcardType and upperBound + */ + public static ResolvableType forWildCardTypeWithUpperBound(WildcardType wildcardType, ResolvableType upperBound) { + Assert.notNull(wildcardType, "WildcardType must not be null"); + Assert.notNull(upperBound, "UpperBound must not be null"); + Type[] originalLowerBound = wildcardType.getLowerBounds(); + Assert.isTrue(originalLowerBound.length == 0, + () -> "The WildcardType has lower bound while upper bound provided " + wildcardType); + + Type upperBoundType = upperBound.getType(); + VariableResolver variableResolver = upperBoundType instanceof TypeVariable typeVariable + ? new TypeVariablesVariableResolver( + new TypeVariable[]{typeVariable}, new ResolvableType[]{upperBound}) + : null; + + return forType(new WildcardTypeImpl(new Type[]{upperBoundType}, EMPTY_TYPE_ARRAY), variableResolver); + } + + /** + * Return a {@code ResolvableType} for the specified {@link WildcardType} with pre-declared lower bound. + * @param wildcardType the WildcardType to introspect + * @param lowerBound the lower bound of the wildcardType + * @return a {@code ResolvableType} for the specific wildcardType and lowerBound + */ + public static ResolvableType forWildCardTypeWithLowerBound(WildcardType wildcardType, ResolvableType lowerBound) { + Assert.notNull(wildcardType, "WildcardType must not be null"); + Assert.notNull(lowerBound, "LowerBound must not be null"); + Type[] originalUpperBound = wildcardType.getUpperBounds(); + Assert.isTrue(originalUpperBound.length == 0 || originalUpperBound[0] == Object.class, + () -> "The WildcardType has upper bound %s while lower bound provided %s" + .formatted(originalUpperBound[0], wildcardType)); + + Type lowerBoundType = lowerBound.getType(); + VariableResolver variableResolver = lowerBoundType instanceof TypeVariable typeVariable + ? new TypeVariablesVariableResolver( + new TypeVariable[]{typeVariable}, new ResolvableType[]{lowerBound}) + : null; + + return forType(new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBoundType}), variableResolver); + } + /** * Return a {@code ResolvableType} for the specified instance. The instance does not * convey generic information but if it implements {@link ResolvableTypeProvider} a @@ -1628,6 +1694,56 @@ public Object getSource() { } + private static final class WildcardTypeImpl implements WildcardType, Serializable { + + private final Type[] upperBound; + private final Type[] lowerBound; + + private WildcardTypeImpl(Type[] upperBound, Type[] lowerBound) { + this.upperBound = upperBound; + this.lowerBound = lowerBound; + } + + @Override + public Type[] getUpperBounds() { + return upperBound.clone(); + } + + @Override + public Type[] getLowerBounds() { + return lowerBound.clone(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof WildcardType that)) { + return false; + } + return Arrays.equals(upperBound, that.getUpperBounds()) && Arrays.equals(lowerBound, that.getLowerBounds()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); + } + + @Override + public String toString() { + if (getLowerBounds().length == 1) { + return "? super " + typeToString(getLowerBounds()[0]); + } + if (getUpperBounds().length == 0 || getUpperBounds()[0] == Object.class) { + return "?"; + } + return "? extends " + typeToString(getUpperBounds()[0]); + } + + private static String typeToString(Type type) { + return type instanceof Class cls ? cls.getName() : type.toString(); + } + } + + private static final class SyntheticParameterizedType implements ParameterizedType, Serializable { private final Type rawType; 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 7e4e3542c021..13dc4d92606c 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -22,6 +22,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,6 +30,8 @@ import java.util.function.Supplier; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.core.GenericTypeResolver.getTypeVariableMap; @@ -251,6 +254,14 @@ void resolveTypeFromGenericDefaultMethod() { assertThat(resolvedType).isEqualTo(InheritsDefaultMethod.ConcreteType.class); } + @ParameterizedTest + @ValueSource(strings = {"getUpperBound", "getLowerBound"}) + void resolveTypeFromWildcardType(String methodName) { + Type type = method(MyInterfaceType.class, methodName).getGenericReturnType(); + Type resolvedType = resolveType(type, MySimpleInterfaceType.class); + assertThat(resolvedType).isEqualTo(method(MySimpleInterfaceType.class, methodName).getGenericReturnType()); + } + @Test void resolveTypeFromNestedParameterizedType() { Type resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyCollectionInterfaceType.class); @@ -268,12 +279,29 @@ private static Method method(Class target, String methodName, Class... par public interface MyInterfaceType { + default Optional getUpperBound() { + return Optional.empty(); + } + + default List getLowerBound() { + return Collections.emptyList(); + } + default T get() { return null; } } public class MySimpleInterfaceType implements MyInterfaceType { + @Override + public Optional getUpperBound() { + return MyInterfaceType.super.getUpperBound(); + } + + @Override + public List getLowerBound() { + return MyInterfaceType.super.getLowerBound(); + } } public class MyParameterizedInterfaceType

implements MyInterfaceType> { diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 8b67d70d7835..c0ceab389923 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -41,10 +41,13 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.Callable; +import java.util.stream.Stream; import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.junit.jupiter.MockitoExtension; @@ -1549,6 +1552,31 @@ void gh34541() throws Exception { assertThat(typeWithGenerics.isAssignableFrom(PaymentCreator.class)).isTrue(); } + @ParameterizedTest + @MethodSource("wildcardInfo") + void gh36474(ResolvableType typeVariable, Class resolved) { + assertThat(typeVariable.resolve()).isEqualTo(resolved); + } + + + static Stream wildcardInfo() throws Exception { + WildcardType listxs = getWildcardType(AssignmentBase.class, "listxs"); + WildcardType listsc = getWildcardType(AssignmentBase.class, "listsc"); + ResolvableType owner = ResolvableType.forType(Assignment.class).as(AssignmentBase.class); + + ResolvableType lbWildcard = ResolvableType.forWildCardTypeWithUpperBound( + listxs, ResolvableType.forType(listxs.getUpperBounds()[0], owner)); + ResolvableType ubWildcard = ResolvableType.forWildCardTypeWithLowerBound( + listsc, ResolvableType.forType(listsc.getLowerBounds()[0], owner)); + return Stream.of(new Object[] {lbWildcard, String.class}, new Object[] {ubWildcard, CharSequence.class}); + } + + + static WildcardType getWildcardType(Class cls, String field) throws Exception { + ResolvableType type = ResolvableType.forField(cls.getField(field)); + return (WildcardType) type.getGeneric(0).getType(); + } + private ResolvableType testSerialization(ResolvableType type) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -1579,13 +1607,13 @@ private static ResolvableTypeAssert assertThatResolvableType(ResolvableType type @SuppressWarnings("unused") private HashMap> myMap; - @SuppressWarnings("serial") static class ExtendsList extends ArrayList { - } + } @SuppressWarnings("serial") static class ExtendsMap extends HashMap { + } From 0e4c50b72ef684a2344e18dd537898b01f5e7779 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 16 Mar 2026 16:07:21 +0700 Subject: [PATCH 3/7] fix Signed-off-by: anaconda875 --- .../main/java/org/springframework/core/ResolvableType.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 8b51ecb0b2ce..8789b37ee7ff 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -619,6 +619,7 @@ private boolean determineUnresolvableGenerics(@Nullable Set alreadySeen) { ResolvableType[] generics = getGenerics(); for (ResolvableType generic : generics) { if (generic.isUnresolvableTypeVariable() || + generic.isWildcardWithoutBounds() || generic.isUnresolvableWildcard(currentTypeSeen(alreadySeen)) || generic.hasUnresolvableGenerics(currentTypeSeen(alreadySeen))) { return true; @@ -687,7 +688,7 @@ private boolean isWildcardWithoutBounds() { /** * Determine whether the underlying type represents a wildcard - * has unresolvable upper bound or lower bound, or simply without bound + * has unresolvable upper bound or lower bound */ private boolean isUnresolvableWildcard(Set alreadySeen) { if (this.type instanceof WildcardType wildcardType) { @@ -702,7 +703,7 @@ private boolean isUnresolvableWildcard(Set alreadySeen) { return upperResolvable.isUnresolvableTypeVariable() || upperResolvable.determineUnresolvableGenerics(alreadySeen); } } - return isWildcardWithoutBounds(); + return false; } /** From 6b053afceee55fed3ce887486ce4edec0f9b6db7 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 23 Mar 2026 21:07:31 +0700 Subject: [PATCH 4/7] Fix: rebase with main and clean up Signed-off-by: anaconda875 --- .../java/org/springframework/core/GenericTypeResolver.java | 4 ++-- .../org/springframework/core/GenericTypeResolverTests.java | 5 +++-- .../java/org/springframework/core/ResolvableTypeTests.java | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 1cd981de6c38..7f20723b8d92 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -209,7 +209,8 @@ else if (genericType instanceof WildcardType wildcardType) { wildcardType, ResolvableType.forType(lowerBound)) .getType(); } - } else if (originalUpperBound.length == 1) { + } + else if (originalUpperBound.length == 1) { Type upperBound = resolveType(originalUpperBound[0], contextClass); if (upperBound != originalUpperBound[0]) { return ResolvableType.forWildCardTypeWithUpperBound( @@ -217,7 +218,6 @@ else if (genericType instanceof WildcardType wildcardType) { .getType(); } } - return wildcardType; } } return genericType; 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 13dc4d92606c..8378d15cc9b6 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -264,10 +264,11 @@ void resolveTypeFromWildcardType(String methodName) { @Test void resolveTypeFromNestedParameterizedType() { - Type resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyCollectionInterfaceType.class); + Type rawReturnType = method(MyInterfaceType.class, "get").getGenericReturnType(); + Type resolvedType = resolveType(rawReturnType, MyCollectionInterfaceType.class); assertThat(resolvedType).isEqualTo(method(MyCollectionInterfaceType.class, "get").getGenericReturnType()); - resolvedType = resolveType(method(MyInterfaceType.class, "get").getGenericReturnType(), MyOptionalInterfaceType.class); + resolvedType = resolveType(rawReturnType, MyOptionalInterfaceType.class); assertThat(resolvedType).isEqualTo(method(MyOptionalInterfaceType.class, "get").getGenericReturnType()); } diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index c0ceab389923..afbeeebde1d2 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1607,13 +1607,13 @@ private static ResolvableTypeAssert assertThatResolvableType(ResolvableType type @SuppressWarnings("unused") private HashMap> myMap; + @SuppressWarnings("serial") static class ExtendsList extends ArrayList { - } + @SuppressWarnings("serial") static class ExtendsMap extends HashMap { - } From e18968cb3a5e651bde14a6ce7feb7f11d8a04ab5 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 23 Mar 2026 21:31:45 +0700 Subject: [PATCH 5/7] Fix: Checkstyle Signed-off-by: anaconda875 --- .../springframework/core/ResolvableType.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 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 8789b37ee7ff..e580347d53a7 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -688,7 +688,7 @@ private boolean isWildcardWithoutBounds() { /** * Determine whether the underlying type represents a wildcard - * has unresolvable upper bound or lower bound + * has unresolvable upper bound or lower bound. */ private boolean isUnresolvableWildcard(Set alreadySeen) { if (this.type instanceof WildcardType wildcardType) { @@ -1221,10 +1221,10 @@ public static ResolvableType forWildCardTypeWithUpperBound(WildcardType wildcard () -> "The WildcardType has lower bound while upper bound provided " + wildcardType); Type upperBoundType = upperBound.getType(); - VariableResolver variableResolver = upperBoundType instanceof TypeVariable typeVariable - ? new TypeVariablesVariableResolver( - new TypeVariable[]{typeVariable}, new ResolvableType[]{upperBound}) - : null; + VariableResolver variableResolver = upperBoundType instanceof TypeVariable typeVariable ? + new TypeVariablesVariableResolver( + new TypeVariable[]{typeVariable}, new ResolvableType[]{upperBound}) : + null; return forType(new WildcardTypeImpl(new Type[]{upperBoundType}, EMPTY_TYPE_ARRAY), variableResolver); } @@ -1244,10 +1244,10 @@ public static ResolvableType forWildCardTypeWithLowerBound(WildcardType wildcard .formatted(originalUpperBound[0], wildcardType)); Type lowerBoundType = lowerBound.getType(); - VariableResolver variableResolver = lowerBoundType instanceof TypeVariable typeVariable - ? new TypeVariablesVariableResolver( - new TypeVariable[]{typeVariable}, new ResolvableType[]{lowerBound}) - : null; + VariableResolver variableResolver = lowerBoundType instanceof TypeVariable typeVariable ? + new TypeVariablesVariableResolver( + new TypeVariable[]{typeVariable}, new ResolvableType[]{lowerBound}) : + null; return forType(new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBoundType}), variableResolver); } @@ -1695,9 +1695,10 @@ public Object getSource() { } - private static final class WildcardTypeImpl implements WildcardType, Serializable { + private static final class WildcardTypeImpl implements WildcardType, Serializable { private final Type[] upperBound; + private final Type[] lowerBound; private WildcardTypeImpl(Type[] upperBound, Type[] lowerBound) { @@ -1707,12 +1708,12 @@ private WildcardTypeImpl(Type[] upperBound, Type[] lowerBound) { @Override public Type[] getUpperBounds() { - return upperBound.clone(); + return this.upperBound.clone(); } @Override public Type[] getLowerBounds() { - return lowerBound.clone(); + return this.lowerBound.clone(); } @Override @@ -1720,7 +1721,8 @@ public boolean equals(Object o) { if (!(o instanceof WildcardType that)) { return false; } - return Arrays.equals(upperBound, that.getUpperBounds()) && Arrays.equals(lowerBound, that.getLowerBounds()); + return Arrays.equals(this.upperBound, that.getUpperBounds()) && + Arrays.equals(this.lowerBound, that.getLowerBounds()); } @Override From 517bd6bdf72196fbeb729d6769295bdc5b9902e4 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Mon, 23 Mar 2026 22:16:11 +0700 Subject: [PATCH 6/7] Fix: refactor methods name Signed-off-by: anaconda875 --- .../java/org/springframework/core/GenericTypeResolver.java | 4 ++-- .../main/java/org/springframework/core/ResolvableType.java | 4 ++-- .../java/org/springframework/core/ResolvableTypeTests.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 7f20723b8d92..2e82f413ead9 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -205,7 +205,7 @@ else if (genericType instanceof WildcardType wildcardType) { if (originalLowerBound.length == 1) { Type lowerBound = resolveType(originalLowerBound[0], contextClass); if (lowerBound != originalLowerBound[0]) { - return ResolvableType.forWildCardTypeWithLowerBound( + return ResolvableType.forWildcardTypeWithLowerBound( wildcardType, ResolvableType.forType(lowerBound)) .getType(); } @@ -213,7 +213,7 @@ else if (genericType instanceof WildcardType wildcardType) { else if (originalUpperBound.length == 1) { Type upperBound = resolveType(originalUpperBound[0], contextClass); if (upperBound != originalUpperBound[0]) { - return ResolvableType.forWildCardTypeWithUpperBound( + return ResolvableType.forWildcardTypeWithUpperBound( wildcardType, ResolvableType.forType(upperBound)) .getType(); } 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 e580347d53a7..7de67898a57e 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -1213,7 +1213,7 @@ public static ResolvableType forClassWithGenerics(Class clazz, @Nullable Reso * @param upperBound the upper bound of the wildcardType * @return a {@code ResolvableType} for the specific wildcardType and upperBound */ - public static ResolvableType forWildCardTypeWithUpperBound(WildcardType wildcardType, ResolvableType upperBound) { + public static ResolvableType forWildcardTypeWithUpperBound(WildcardType wildcardType, ResolvableType upperBound) { Assert.notNull(wildcardType, "WildcardType must not be null"); Assert.notNull(upperBound, "UpperBound must not be null"); Type[] originalLowerBound = wildcardType.getLowerBounds(); @@ -1235,7 +1235,7 @@ public static ResolvableType forWildCardTypeWithUpperBound(WildcardType wildcard * @param lowerBound the lower bound of the wildcardType * @return a {@code ResolvableType} for the specific wildcardType and lowerBound */ - public static ResolvableType forWildCardTypeWithLowerBound(WildcardType wildcardType, ResolvableType lowerBound) { + public static ResolvableType forWildcardTypeWithLowerBound(WildcardType wildcardType, ResolvableType lowerBound) { Assert.notNull(wildcardType, "WildcardType must not be null"); Assert.notNull(lowerBound, "LowerBound must not be null"); Type[] originalUpperBound = wildcardType.getUpperBounds(); diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index afbeeebde1d2..6d6d6cbe1aeb 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1564,9 +1564,9 @@ static Stream wildcardInfo() throws Exception { WildcardType listsc = getWildcardType(AssignmentBase.class, "listsc"); ResolvableType owner = ResolvableType.forType(Assignment.class).as(AssignmentBase.class); - ResolvableType lbWildcard = ResolvableType.forWildCardTypeWithUpperBound( + ResolvableType lbWildcard = ResolvableType.forWildcardTypeWithUpperBound( listxs, ResolvableType.forType(listxs.getUpperBounds()[0], owner)); - ResolvableType ubWildcard = ResolvableType.forWildCardTypeWithLowerBound( + ResolvableType ubWildcard = ResolvableType.forWildcardTypeWithLowerBound( listsc, ResolvableType.forType(listsc.getLowerBounds()[0], owner)); return Stream.of(new Object[] {lbWildcard, String.class}, new Object[] {ubWildcard, CharSequence.class}); } From fcb63dbb24d8999746364f9fe32465e69e1d9b89 Mon Sep 17 00:00:00 2001 From: anaconda875 Date: Tue, 24 Mar 2026 20:25:57 +0700 Subject: [PATCH 7/7] Fix: refactor WildcardTypeImpl Signed-off-by: anaconda875 --- .../java/org/springframework/core/ResolvableType.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 7de67898a57e..0c7c14492b9e 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -1727,18 +1727,18 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); + return Arrays.hashCode(this.lowerBound) ^ Arrays.hashCode(this.upperBound); } @Override public String toString() { - if (getLowerBounds().length == 1) { - return "? super " + typeToString(getLowerBounds()[0]); + if (this.lowerBound.length == 1) { + return "? super " + typeToString(this.lowerBound[0]); } - if (getUpperBounds().length == 0 || getUpperBounds()[0] == Object.class) { + if (this.upperBound.length == 0 || this.upperBound[0] == Object.class) { return "?"; } - return "? extends " + typeToString(getUpperBounds()[0]); + return "? extends " + typeToString(this.upperBound[0]); } private static String typeToString(Type type) {