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 a1c515a77b3a..8218407da672 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -329,6 +329,8 @@ private boolean isAssignableFrom(ResolvableType other, boolean strict, return true; } + boolean exactMatch = (strict && matchedBefore != null); // We're checking nested generic variables now... + // Deal with wildcard bounds WildcardBounds ourBounds = WildcardBounds.get(this); WildcardBounds typeBounds = WildcardBounds.get(other); @@ -336,10 +338,14 @@ private boolean isAssignableFrom(ResolvableType other, boolean strict, // In the form X is assignable to if (typeBounds != null) { if (ourBounds != null) { - return (ourBounds.isSameKind(typeBounds) && ourBounds.isAssignableFrom(typeBounds.getBounds())); + return (ourBounds.isSameKind(typeBounds) && + ourBounds.isAssignableFrom(typeBounds.getBounds(), matchedBefore)); } else if (upUntilUnresolvable) { - return typeBounds.isAssignableFrom(this); + return typeBounds.isAssignableFrom(this, matchedBefore); + } + else if (!exactMatch) { + return typeBounds.isAssignableTo(this, matchedBefore); } else { return false; @@ -348,11 +354,10 @@ else if (upUntilUnresolvable) { // In the form is assignable to X... if (ourBounds != null) { - return ourBounds.isAssignableFrom(other); + return ourBounds.isAssignableFrom(other, matchedBefore); } // Main assignability check about to follow - boolean exactMatch = (matchedBefore != null); // We're checking nested generic variables now... boolean checkGenerics = true; Class ourResolved = null; if (this.type instanceof TypeVariable variable) { @@ -667,9 +672,9 @@ private boolean isUnresolvableTypeVariable() { * without specific bounds (i.e., equal to {@code ? extends Object}). */ private boolean isWildcardWithoutBounds() { - if (this.type instanceof WildcardType wt) { - if (wt.getLowerBounds().length == 0) { - Type[] upperBounds = wt.getUpperBounds(); + 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; } @@ -1693,30 +1698,60 @@ public WildcardBounds(Kind kind, ResolvableType[] bounds) { } /** - * Return {@code true} if this bounds is the same kind as the specified bounds. + * Return {@code true} if these bounds are the same kind as the specified bounds. */ public boolean isSameKind(WildcardBounds bounds) { return this.kind == bounds.kind; } /** - * Return {@code true} if this bounds is assignable to all the specified types. + * Return {@code true} if these bounds are assignable from all the specified types. * @param types the types to test against - * @return {@code true} if this bounds is assignable to all types + * @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)) { + return false; + } + } + return true; + } + + /** + * Return {@code true} if these bounds are assignable from the specified type. + * @param type the type to test against + * @return {@code true} if these bounds are assignable from the type + * @since 6.2 */ - public boolean isAssignableFrom(ResolvableType... types) { + public boolean isAssignableFrom(ResolvableType type, @Nullable Map matchedBefore) { for (ResolvableType bound : this.bounds) { - for (ResolvableType type : types) { - if (!isAssignable(bound, type)) { - return false; - } + if (this.kind == Kind.UPPER ? !bound.isAssignableFrom(type, false, matchedBefore, false) : + !type.isAssignableFrom(bound, false, matchedBefore, false)) { + return false; } } return true; } - private boolean isAssignable(ResolvableType source, ResolvableType from) { - return (this.kind == Kind.UPPER ? source.isAssignableFrom(from) : from.isAssignableFrom(source)); + /** + * Return {@code true} if these bounds are assignable to the specified type. + * @param type the type to test against + * @return {@code true} if these bounds are assignable to the type + * @since 6.2 + */ + public boolean isAssignableTo(ResolvableType type, @Nullable Map matchedBefore) { + if (this.kind == Kind.UPPER) { + for (ResolvableType bound : this.bounds) { + if (type.isAssignableFrom(bound, false, matchedBefore, false)) { + return true; + } + } + return false; + } + else { + return (type.resolve() == Object.class); + } } /** @@ -1728,21 +1763,30 @@ public ResolvableType[] getBounds() { /** * Get a {@link WildcardBounds} instance for the specified type, returning - * {@code null} if the specified type cannot be resolved to a {@link WildcardType}. + * {@code null} if the specified type cannot be resolved to a {@link WildcardType} + * or an equivalent unresolvable type variable. * @param type the source type * @return a {@link WildcardBounds} instance or {@code null} */ @Nullable public static WildcardBounds get(ResolvableType type) { - ResolvableType resolveToWildcard = type; - while (!(resolveToWildcard.getType() instanceof WildcardType wildcardType)) { - if (resolveToWildcard == NONE) { + ResolvableType candidate = type; + while (!(candidate.getType() instanceof WildcardType || candidate.isUnresolvableTypeVariable())) { + if (candidate == NONE) { return null; } - resolveToWildcard = resolveToWildcard.resolveType(); + candidate = candidate.resolveType(); + } + Kind boundsType; + Type[] bounds; + if (candidate.getType() instanceof WildcardType wildcardType) { + boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER); + bounds = (boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds()); + } + else { + boundsType = Kind.UPPER; + bounds = ((TypeVariable) candidate.getType()).getBounds(); } - Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER); - Type[] bounds = (boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds()); ResolvableType[] resolvableBounds = new ResolvableType[bounds.length]; for (int i = 0; i < bounds.length; i++) { resolvableBounds[i] = ResolvableType.forType(bounds[i], type.variableResolver); 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 966793c564b5..8e8882b7b7f5 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1023,11 +1023,21 @@ void isAssignableFromForClassAndClass() { @Test void isAssignableFromCannotBeResolved() throws Exception { ResolvableType objectType = ResolvableType.forClass(Object.class); - ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o")); + ResolvableType unresolvableVariable1 = ResolvableType.forField(AssignmentBase.class.getField("o")); + ResolvableType unresolvableVariable2 = ResolvableType.forField(AssignmentBase.class.getField("c")); + ResolvableType unresolvableVariable3 = ResolvableType.forField(AssignmentBase.class.getField("s")); - assertThat(unresolvableVariable.resolve()).isNull(); - assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable); - assertThatResolvableType(unresolvableVariable).isAssignableFrom(objectType); + assertThat(unresolvableVariable1.resolve()).isNull(); + assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable1); + assertThatResolvableType(unresolvableVariable1).isAssignableFrom(objectType); + + assertThat(unresolvableVariable2.resolve()).isNull(); + assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable2); + assertThatResolvableType(unresolvableVariable2).isAssignableFrom(objectType); + + assertThat(unresolvableVariable3.resolve()).isEqualTo(Serializable.class); + assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable3); + assertThatResolvableType(unresolvableVariable3).isNotAssignableFrom(objectType); } @Test @@ -1157,7 +1167,7 @@ void isAssignableFromForWildcards() throws Exception { // T <= ? extends T assertThatResolvableType(extendsCharSequence).isAssignableFrom(charSequence, string).isNotAssignableFrom(object); - assertThatResolvableType(charSequence).isNotAssignableFrom(extendsObject, extendsCharSequence, extendsString); + assertThatResolvableType(charSequence).isAssignableFrom(extendsCharSequence, extendsString).isNotAssignableFrom(extendsObject); assertThatResolvableType(extendsAnon).isAssignableFrom(object, charSequence, string); // T <= ? super T @@ -1367,6 +1377,14 @@ void spr16456() throws Exception { assertThat(type.resolveGeneric()).isEqualTo(Integer.class); } + @Test + void gh22902() throws Exception { + ResolvableType ab = ResolvableType.forField(ABClient.class.getField("field")); + assertThat(ab.isAssignableFrom(Object.class)).isFalse(); + assertThat(ab.isAssignableFrom(AwithB.class)).isTrue(); + assertThat(ab.isAssignableFrom(AwithoutB.class)).isFalse(); + } + @Test void gh32327() throws Exception { ResolvableType repository1 = ResolvableType.forField(Fields.class.getField("repository")); @@ -1375,7 +1393,7 @@ void gh32327() throws Exception { assertThat(repository1.hasUnresolvableGenerics()).isFalse(); assertThat(repository1.isAssignableFrom(repository2)).isFalse(); assertThat(repository1.isAssignableFromResolvedPart(repository2)).isTrue(); - assertThat(repository1.isAssignableFrom(repository3)).isFalse(); + assertThat(repository1.isAssignableFrom(repository3)).isTrue(); assertThat(repository1.isAssignableFromResolvedPart(repository3)).isTrue(); assertThat(repository2.hasUnresolvableGenerics()).isTrue(); assertThat(repository2.isAssignableFrom(repository1)).isTrue(); @@ -1520,7 +1538,7 @@ interface TypedMethods extends Methods { } - static class AssignmentBase { + static class AssignmentBase { public O o; @@ -1722,6 +1740,40 @@ public abstract class UnresolvedWithGenerics { } + interface A { + + void doA(); + } + + interface B { + + void doB(); + } + + static class ABClient { + + public T field; + } + + static class AwithB implements A, B { + + @Override + public void doA() { + } + + @Override + public void doB() { + } + } + + static class AwithoutB implements A { + + @Override + public void doA() { + } + } + + private static class ResolvableTypeAssert extends AbstractAssert{ public ResolvableTypeAssert(ResolvableType actual) {