Add support for resolving multiple bounds in type variables

Closes gh-22902
See gh-32327
This commit is contained in:
Juergen Hoeller 2024-03-02 11:30:17 +01:00
parent ac1a030c35
commit 390fe0fe78
2 changed files with 128 additions and 32 deletions

View File

@ -329,6 +329,8 @@ public class ResolvableType implements Serializable {
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 @@ public class ResolvableType implements Serializable {
// In the form X is assignable to <? extends Number>
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 @@ public class ResolvableType implements Serializable {
// In the form <? extends Number> 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 @@ public class ResolvableType implements Serializable {
* 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 class ResolvableType implements Serializable {
}
/**
* 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) {
for (ResolvableType bound : this.bounds) {
for (ResolvableType type : types) {
if (!isAssignable(bound, type)) {
return false;
}
public boolean isAssignableFrom(ResolvableType[] types, @Nullable Map<Type, Type> matchedBefore) {
for (ResolvableType type : types) {
if (!isAssignableFrom(type, matchedBefore)) {
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 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 type, @Nullable Map<Type, Type> matchedBefore) {
for (ResolvableType bound : this.bounds) {
if (this.kind == Kind.UPPER ? !bound.isAssignableFrom(type, false, matchedBefore, false) :
!type.isAssignableFrom(bound, false, matchedBefore, false)) {
return false;
}
}
return true;
}
/**
* 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<Type, Type> 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 class ResolvableType implements Serializable {
/**
* 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);

View File

@ -1023,11 +1023,21 @@ class ResolvableTypeTests {
@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 @@ class ResolvableTypeTests {
// 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 @@ class ResolvableTypeTests {
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 @@ class ResolvableTypeTests {
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 @@ class ResolvableTypeTests {
}
static class AssignmentBase<O, C, S> {
static class AssignmentBase<O, C, S extends Serializable> {
public O o;
@ -1722,6 +1740,40 @@ class ResolvableTypeTests {
}
interface A {
void doA();
}
interface B {
void doB();
}
static class ABClient<T extends A & B> {
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<ResolvableTypeAssert, ResolvableType>{
public ResolvableTypeAssert(ResolvableType actual) {