From 4c3b435d232ca2c157167898893ab9e4715e19fd Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 30 Jan 2025 15:34:55 +0100 Subject: [PATCH 1/2] Enforce exact match for bounds of nested type variable Closes gh-34300 --- .../springframework/core/ResolvableType.java | 24 +++++++++++++++---- .../core/ResolvableTypeTests.java | 9 +++++++ 2 files changed, 28 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 1929c4ea51d..30c3c947d72 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -329,9 +329,6 @@ private boolean isAssignableFrom(ResolvableType other, boolean strict, other.getComponentType(), true, matchedBefore, upUntilUnresolvable)); } - // We're checking nested generic variables now... - boolean exactMatch = (strict && matchedBefore != null); - // Deal with wildcard bounds WildcardBounds ourBounds = WildcardBounds.get(this); WildcardBounds otherBounds = WildcardBounds.get(other); @@ -345,8 +342,9 @@ private boolean isAssignableFrom(ResolvableType other, boolean strict, else if (upUntilUnresolvable) { return otherBounds.isAssignableFrom(this, matchedBefore); } - else if (!exactMatch) { - return otherBounds.isAssignableTo(this, matchedBefore); + else if (!strict) { + return (matchedBefore != null ? otherBounds.equalsType(this) : + otherBounds.isAssignableTo(this, matchedBefore)); } else { return false; @@ -359,6 +357,7 @@ else if (!exactMatch) { } // Main assignability check about to follow + boolean exactMatch = (strict && matchedBefore != null); boolean checkGenerics = true; Class ourResolved = null; if (this.type instanceof TypeVariable variable) { @@ -1782,6 +1781,21 @@ public boolean isAssignableTo(ResolvableType type, @Nullable Map mat } } + /** + * Return {@code true} if these bounds are equal to the specified type. + * @param type the type to test against + * @return {@code true} if these bounds are equal to the type + * @since 6.2.3 + */ + public boolean equalsType(ResolvableType type) { + for (ResolvableType bound : this.bounds) { + if (!type.equalsType(bound)) { + return false; + } + } + return true; + } + /** * Return the underlying bounds. */ 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 29db287ee8c..dcfc31a897e 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1230,6 +1230,8 @@ void strictGenericsMatching() { ResolvableType consumerUnresolved = ResolvableType.forClass(Consumer.class); ResolvableType consumerObject = ResolvableType.forClassWithGenerics(Consumer.class, Object.class); ResolvableType consumerNestedUnresolved = ResolvableType.forClassWithGenerics(Consumer.class, ResolvableType.forClass(Consumer.class)); + ResolvableType consumerNumber = ResolvableType.forClassWithGenerics(Consumer.class, Number.class); + ResolvableType consumerExtendsNumber = ResolvableType.forClass(SubConsumer.class); assertThat(consumerUnresolved.isAssignableFrom(consumerObject)).isTrue(); assertThat(consumerUnresolved.isAssignableFromResolvedPart(consumerObject)).isTrue(); @@ -1239,6 +1241,10 @@ void strictGenericsMatching() { assertThat(consumerUnresolved.isAssignableFromResolvedPart(consumerNestedUnresolved)).isTrue(); assertThat(consumerObject.isAssignableFrom(consumerNestedUnresolved)).isFalse(); assertThat(consumerObject.isAssignableFromResolvedPart(consumerNestedUnresolved)).isFalse(); + assertThat(consumerObject.isAssignableFrom(consumerNumber)).isFalse(); + assertThat(consumerObject.isAssignableFromResolvedPart(consumerNumber)).isFalse(); + assertThat(consumerObject.isAssignableFrom(consumerExtendsNumber)).isFalse(); + assertThat(consumerObject.isAssignableFromResolvedPart(consumerExtendsNumber)).isTrue(); } @Test @@ -1788,6 +1794,9 @@ public class MyCollectionSuperclassType extends MySuperclassType { } + private static class SubConsumer implements Consumer { + } + public class Wildcard { } From ed994dcd977b5a48db00c19a7a296b649d8f27c0 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 30 Jan 2025 15:35:14 +0100 Subject: [PATCH 2/2] Resolve bounds for type variable before emptiness check Closes gh-34328 --- .../core/GenericTypeResolver.java | 4 +- .../springframework/core/ResolvableType.java | 27 ++++++++----- .../core/GenericTypeResolverTests.java | 39 +++++++++++++------ 3 files changed, 48 insertions(+), 22 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 c27a23cd07e..9e4a9e381a8 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -180,7 +180,7 @@ else if (genericType instanceof ParameterizedType parameterizedType) { generics[i] = resolvedTypeArgument; } else { - generics[i] = ResolvableType.forType(typeArgument).resolveType(); + generics[i] = ResolvableType.forType(typeArgument); } } else if (typeArgument instanceof ParameterizedType) { @@ -226,7 +226,7 @@ private static ResolvableType resolveVariable(TypeVariable typeVariable, Reso return resolvedType; } } - return ResolvableType.NONE; + return ResolvableType.forVariableBounds(typeVariable); } /** 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 30c3c947d72..594beef070a 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -953,14 +953,6 @@ ResolvableType resolveType() { return NONE; } - @Nullable - private Type resolveBounds(Type[] bounds) { - if (bounds.length == 0 || bounds[0] == Object.class) { - return null; - } - return bounds[0]; - } - @Nullable private ResolvableType resolveVariable(TypeVariable variable) { if (this.type instanceof TypeVariable) { @@ -1463,6 +1455,24 @@ public static ResolvableType forArrayComponent(ResolvableType componentType) { return new ResolvableType(arrayType, componentType, null, null); } + /** + * Return a {@code ResolvableType} for the bounds of the specified {@link TypeVariable}. + * @param typeVariable the type variable + * @return a {@code ResolvableType} for the specified bounds + * @since 6.2.3 + */ + static ResolvableType forVariableBounds(TypeVariable typeVariable) { + return forType(resolveBounds(typeVariable.getBounds())); + } + + @Nullable + private static Type resolveBounds(Type[] bounds) { + if (bounds.length == 0 || bounds[0] == Object.class) { + return null; + } + return bounds[0]; + } + /** * Return a {@code ResolvableType} for the specified {@link Type}. *

Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}. @@ -1491,7 +1501,6 @@ public static ResolvableType forType(@Nullable Type type, @Nullable ResolvableTy return forType(type, variableResolver); } - /** * Return a {@code ResolvableType} for the specified {@link ParameterizedTypeReference}. *

Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}. 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 1f80cc6c343..fcfff993433 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -212,19 +212,27 @@ void resolveNestedTypeVariable() throws Exception { } @Test - void resolvedTypeWithBase() { - Type type = method(WithBaseTypes.class, "get").getGenericReturnType(); - Type resolvedType = resolveType(type, WithBaseTypes.class); + void resolveTypeWithElementBounds() { + Type type = method(WithElementBounds.class, "get").getGenericReturnType(); + Type resolvedType = resolveType(type, WithElementBounds.class); ParameterizedTypeReference> reference = new ParameterizedTypeReference<>() {}; assertThat(resolvedType).isEqualTo(reference.getType()); } + @Test + void resolveTypeWithUnresolvableElement() { + Type type = method(WithUnresolvableElement.class, "get").getGenericReturnType(); + Type resolvedType = resolveType(type, WithUnresolvableElement.class); + assertThat(resolvedType.toString()).isEqualTo("java.util.List"); + } + private static Method method(Class target, String methodName, Class... parameterTypes) { Method method = findMethod(target, methodName, parameterTypes); assertThat(method).describedAs(target.getName() + "#" + methodName).isNotNull(); return method; } + public interface MyInterfaceType { } @@ -348,9 +356,9 @@ public static class MySimpleTypeWithMethods extends MyTypeWithMethods { static class GenericClass { } - class A{} + class A {} - class B{} + class B {} class C extends A {} @@ -358,23 +366,25 @@ class D extends B {} class E extends C {} - class TestIfc{} + class TestIfc {} - class TestImpl> extends TestIfc{ + class TestImpl> extends TestIfc { } abstract static class BiGenericClass, V extends A> {} - abstract static class SpecializedBiGenericClass extends BiGenericClass{} + abstract static class SpecializedBiGenericClass extends BiGenericClass {} static class TypeFixedBiGenericClass extends SpecializedBiGenericClass {} static class TopLevelClass { + class Nested { } } static class TypedTopLevelClass extends TopLevelClass { + class TypedNested extends Nested { } } @@ -394,24 +404,31 @@ interface IdFixingRepository extends Repository { } static class WithMethodParameter { + public void nestedGenerics(List> input) { } } - public interface ListOfListSupplier { + interface ListOfListSupplier { List> get(); } - public interface StringListOfListSupplier extends ListOfListSupplier { + interface StringListOfListSupplier extends ListOfListSupplier { } - static class WithBaseTypes { + static class WithElementBounds { List get() { return List.of(); } + } + + static class WithUnresolvableElement { + List get() { + return List.of(); + } } }