Add support for resolving multiple bounds in type variables
Closes gh-22902 See gh-32327
This commit is contained in:
parent
ac1a030c35
commit
390fe0fe78
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue