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 aa72d9bc07..b9d46a3734 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -177,7 +177,7 @@ public final class GenericTypeResolver { generics[i] = resolvedTypeArgument; } else { - generics[i] = ResolvableType.forType(typeArgument).resolveType(); + generics[i] = ResolvableType.forType(typeArgument); } } else if (typeArgument instanceof ParameterizedType) { @@ -223,7 +223,7 @@ public final class GenericTypeResolver { 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 813562b9b4..fc8e60edd3 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -320,9 +320,6 @@ public class ResolvableType implements Serializable { 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); @@ -336,8 +333,9 @@ public class ResolvableType implements Serializable { 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; @@ -350,6 +348,7 @@ public class ResolvableType implements Serializable { } // Main assignability check about to follow + boolean exactMatch = (strict && matchedBefore != null); boolean checkGenerics = true; Class ourResolved = null; if (this.type instanceof TypeVariable variable) { @@ -942,13 +941,6 @@ public class ResolvableType implements Serializable { return NONE; } - private @Nullable Type resolveBounds(Type[] bounds) { - if (bounds.length == 0 || bounds[0] == Object.class) { - return null; - } - return bounds[0]; - } - private @Nullable ResolvableType resolveVariable(TypeVariable variable) { if (this.type instanceof TypeVariable) { return resolveType().resolveVariable(variable); @@ -1449,6 +1441,23 @@ public class ResolvableType implements Serializable { 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())); + } + + private static @Nullable 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}. @@ -1477,7 +1486,6 @@ public class ResolvableType implements Serializable { return forType(type, variableResolver); } - /** * Return a {@code ResolvableType} for the specified {@link ParameterizedTypeReference}. *

Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}. @@ -1763,6 +1771,21 @@ public class ResolvableType implements Serializable { } } + /** + * 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/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 1f80cc6c34..fcfff99343 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 @@ class GenericTypeResolverTests { } @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 @@ class GenericTypeResolverTests { static class GenericClass { } - class A{} + class A {} - class B{} + class B {} class C extends A {} @@ -358,23 +366,25 @@ class GenericTypeResolverTests { 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 @@ class GenericTypeResolverTests { } 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(); + } } } 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 29db287ee8..dcfc31a897 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 @@ class ResolvableTypeTests { 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 @@ class ResolvableTypeTests { 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 @@ class ResolvableTypeTests { public interface Consumer { } + private static class SubConsumer implements Consumer { + } + public class Wildcard { }