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 d8d4ac98e7fc..1929c4ea51de 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -410,8 +410,9 @@ else if (!exactMatch) { } matchedBefore.put(this.type, other.type); for (int i = 0; i < ourGenerics.length; i++) { - if (!ourGenerics[i].isAssignableFrom(otherGenerics[i], - !other.hasUnresolvableGenerics(), matchedBefore, upUntilUnresolvable)) { + ResolvableType otherGeneric = otherGenerics[i]; + if (!ourGenerics[i].isAssignableFrom(otherGeneric, + !otherGeneric.isUnresolvableTypeVariable(), matchedBefore, upUntilUnresolvable)) { return false; } } @@ -1729,8 +1730,16 @@ public boolean isSameKind(WildcardBounds bounds) { * @return {@code true} if these bounds are assignable from all types */ public boolean isAssignableFrom(ResolvableType[] types, @Nullable Map matchedBefore) { - for (ResolvableType type : types) { - if (!isAssignableFrom(type, matchedBefore)) { + for (ResolvableType bound : this.bounds) { + boolean matched = false; + for (ResolvableType type : types) { + if (this.kind == Kind.UPPER ? bound.isAssignableFrom(type, false, matchedBefore, false) : + type.isAssignableFrom(bound, false, matchedBefore, false)) { + matched = true; + break; + } + } + if (!matched) { return false; } } 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 c37e2962fbe6..ba3fdde6d1d6 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1189,10 +1189,11 @@ void isAssignableFromForComplexWildcards() throws Exception { } @Test - void isAssignableFromForUnresolvedWildcards() { + void isAssignableFromForUnresolvedWildcard() { ResolvableType wildcard = ResolvableType.forInstance(new Wildcard<>()); ResolvableType wildcardFixed = ResolvableType.forInstance(new WildcardFixed()); - ResolvableType wildcardConcrete = ResolvableType.forClassWithGenerics(Wildcard.class, Number.class); + ResolvableType wildcardConcrete = ResolvableType.forClassWithGenerics(Wildcard.class, CharSequence.class); + ResolvableType wildcardConsumer = ResolvableType.forInstance(new WildcardConsumer<>()); assertThat(wildcard.isAssignableFrom(wildcardFixed)).isTrue(); assertThat(wildcard.isAssignableFromResolvedPart(wildcardFixed)).isTrue(); @@ -1206,6 +1207,38 @@ void isAssignableFromForUnresolvedWildcards() { assertThat(wildcardConcrete.isAssignableFromResolvedPart(wildcard)).isTrue(); assertThat(wildcardConcrete.isAssignableFrom(wildcardFixed)).isFalse(); assertThat(wildcardConcrete.isAssignableFromResolvedPart(wildcardFixed)).isFalse(); + assertThat(wildcardConsumer.as(Consumer.class).getGeneric().isAssignableFrom(wildcard)).isFalse(); + assertThat(wildcardConsumer.as(Consumer.class).getGeneric().isAssignableFromResolvedPart(wildcard)).isTrue(); + } + + @Test + void isAssignableFromForUnresolvedDoubleWildcard() { + ResolvableType wildcard = ResolvableType.forInstance(new DoubleWildcard<>()); + ResolvableType wildcardFixed = ResolvableType.forInstance(new DoubleWildcardFixed()); + ResolvableType wildcardConsumer = ResolvableType.forInstance(new DoubleWildcardConsumer<>()); + + assertThat(wildcard.isAssignableFrom(wildcardFixed)).isTrue(); + assertThat(wildcard.isAssignableFromResolvedPart(wildcardFixed)).isTrue(); + assertThat(wildcardFixed.isAssignableFrom(wildcard)).isFalse(); + assertThat(wildcardFixed.isAssignableFromResolvedPart(wildcard)).isFalse(); + assertThat(wildcardConsumer.as(Consumer.class).getGeneric().isAssignableFrom(wildcard)).isTrue(); + assertThat(wildcardConsumer.as(Consumer.class).getGeneric().isAssignableFromResolvedPart(wildcard)).isTrue(); + } + + @Test + 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)); + + assertThat(consumerUnresolved.isAssignableFrom(consumerObject)).isTrue(); + assertThat(consumerUnresolved.isAssignableFromResolvedPart(consumerObject)).isTrue(); + assertThat(consumerObject.isAssignableFrom(consumerUnresolved)).isTrue(); + assertThat(consumerObject.isAssignableFromResolvedPart(consumerUnresolved)).isTrue(); + assertThat(consumerUnresolved.isAssignableFrom(consumerNestedUnresolved)).isTrue(); + assertThat(consumerUnresolved.isAssignableFromResolvedPart(consumerNestedUnresolved)).isTrue(); + assertThat(consumerObject.isAssignableFrom(consumerNestedUnresolved)).isFalse(); + assertThat(consumerObject.isAssignableFromResolvedPart(consumerNestedUnresolved)).isFalse(); } @Test @@ -1752,12 +1785,26 @@ public class MyCollectionSuperclassType extends MySuperclassType { + public class Wildcard { + } + + public class WildcardFixed extends Wildcard { + } + + public class WildcardConsumer implements Consumer> { } - public class WildcardFixed extends Wildcard { + + public class DoubleWildcard { } + public class DoubleWildcardFixed extends DoubleWildcard { + } + + public class DoubleWildcardConsumer implements Consumer> { + } + + interface VariableNameSwitch extends MultiValueMap { }