Skip to content

Commit

Permalink
Add support for resolving multiple bounds in type variables
Browse files Browse the repository at this point in the history
  • Loading branch information
jhoeller committed Mar 2, 2024
1 parent ac1a030 commit 390fe0f
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -329,17 +329,23 @@ 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);

// 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;
Expand All @@ -348,11 +354,10 @@ else if (upUntilUnresolvable) {

// 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) {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<Type, Type> 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<Type, Type> 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<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);
}
}

/**
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"));
Expand All @@ -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();
Expand Down Expand Up @@ -1520,7 +1538,7 @@ interface TypedMethods extends Methods<String> {
}


static class AssignmentBase<O, C, S> {
static class AssignmentBase<O, C, S extends Serializable> {

public O o;

Expand Down Expand Up @@ -1722,6 +1740,40 @@ public abstract class UnresolvedWithGenerics {
}


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) {
Expand Down

0 comments on commit 390fe0f

Please sign in to comment.