Restrict lenient nested matching to immediate type variable

Includes fix for matching multiple wildcard bounds properly.

Closes gh-34119
Closes gh-34234
This commit is contained in:
Juergen Hoeller 2025-01-15 15:02:46 +01:00
parent d280358e98
commit 227385083d
2 changed files with 66 additions and 10 deletions

View File

@ -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 @@ public class ResolvableType implements Serializable {
}
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 class ResolvableType implements Serializable {
* @return {@code true} if these bounds are assignable from all types
*/
public boolean isAssignableFrom(ResolvableType[] types, @Nullable Map<Type, Type> 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;
}
}

View File

@ -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 @@ class ResolvableTypeTests {
}
@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 @@ class ResolvableTypeTests {
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 @@ class ResolvableTypeTests {
}
public class Wildcard<T extends Number> {
public class Wildcard<T extends CharSequence> {
}
public class WildcardFixed extends Wildcard<Integer> {
public class WildcardFixed extends Wildcard<String> {
}
public class WildcardConsumer<T extends CharSequence & Serializable> implements Consumer<Wildcard<T>> {
}
public class DoubleWildcard<T extends CharSequence & Serializable> {
}
public class DoubleWildcardFixed extends DoubleWildcard<String> {
}
public class DoubleWildcardConsumer<T extends CharSequence & Serializable> implements Consumer<DoubleWildcard<T>> {
}
interface VariableNameSwitch<V, K> extends MultiValueMap<K, V> {
}