Skip to content

Commit 390fe0f

Browse files
committed
Add support for resolving multiple bounds in type variables
Closes gh-22902 See gh-32327
1 parent ac1a030 commit 390fe0f

File tree

2 files changed

+127
-31
lines changed

2 files changed

+127
-31
lines changed

spring-core/src/main/java/org/springframework/core/ResolvableType.java

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -329,17 +329,23 @@ private boolean isAssignableFrom(ResolvableType other, boolean strict,
329329
return true;
330330
}
331331

332+
boolean exactMatch = (strict && matchedBefore != null); // We're checking nested generic variables now...
333+
332334
// Deal with wildcard bounds
333335
WildcardBounds ourBounds = WildcardBounds.get(this);
334336
WildcardBounds typeBounds = WildcardBounds.get(other);
335337

336338
// In the form X is assignable to <? extends Number>
337339
if (typeBounds != null) {
338340
if (ourBounds != null) {
339-
return (ourBounds.isSameKind(typeBounds) && ourBounds.isAssignableFrom(typeBounds.getBounds()));
341+
return (ourBounds.isSameKind(typeBounds) &&
342+
ourBounds.isAssignableFrom(typeBounds.getBounds(), matchedBefore));
340343
}
341344
else if (upUntilUnresolvable) {
342-
return typeBounds.isAssignableFrom(this);
345+
return typeBounds.isAssignableFrom(this, matchedBefore);
346+
}
347+
else if (!exactMatch) {
348+
return typeBounds.isAssignableTo(this, matchedBefore);
343349
}
344350
else {
345351
return false;
@@ -348,11 +354,10 @@ else if (upUntilUnresolvable) {
348354

349355
// In the form <? extends Number> is assignable to X...
350356
if (ourBounds != null) {
351-
return ourBounds.isAssignableFrom(other);
357+
return ourBounds.isAssignableFrom(other, matchedBefore);
352358
}
353359

354360
// Main assignability check about to follow
355-
boolean exactMatch = (matchedBefore != null); // We're checking nested generic variables now...
356361
boolean checkGenerics = true;
357362
Class<?> ourResolved = null;
358363
if (this.type instanceof TypeVariable<?> variable) {
@@ -667,9 +672,9 @@ private boolean isUnresolvableTypeVariable() {
667672
* without specific bounds (i.e., equal to {@code ? extends Object}).
668673
*/
669674
private boolean isWildcardWithoutBounds() {
670-
if (this.type instanceof WildcardType wt) {
671-
if (wt.getLowerBounds().length == 0) {
672-
Type[] upperBounds = wt.getUpperBounds();
675+
if (this.type instanceof WildcardType wildcardType) {
676+
if (wildcardType.getLowerBounds().length == 0) {
677+
Type[] upperBounds = wildcardType.getUpperBounds();
673678
if (upperBounds.length == 0 || (upperBounds.length == 1 && Object.class == upperBounds[0])) {
674679
return true;
675680
}
@@ -1693,30 +1698,60 @@ public WildcardBounds(Kind kind, ResolvableType[] bounds) {
16931698
}
16941699

16951700
/**
1696-
* Return {@code true} if this bounds is the same kind as the specified bounds.
1701+
* Return {@code true} if these bounds are the same kind as the specified bounds.
16971702
*/
16981703
public boolean isSameKind(WildcardBounds bounds) {
16991704
return this.kind == bounds.kind;
17001705
}
17011706

17021707
/**
1703-
* Return {@code true} if this bounds is assignable to all the specified types.
1708+
* Return {@code true} if these bounds are assignable from all the specified types.
17041709
* @param types the types to test against
1705-
* @return {@code true} if this bounds is assignable to all types
1710+
* @return {@code true} if these bounds are assignable from all types
1711+
*/
1712+
public boolean isAssignableFrom(ResolvableType[] types, @Nullable Map<Type, Type> matchedBefore) {
1713+
for (ResolvableType type : types) {
1714+
if (!isAssignableFrom(type, matchedBefore)) {
1715+
return false;
1716+
}
1717+
}
1718+
return true;
1719+
}
1720+
1721+
/**
1722+
* Return {@code true} if these bounds are assignable from the specified type.
1723+
* @param type the type to test against
1724+
* @return {@code true} if these bounds are assignable from the type
1725+
* @since 6.2
17061726
*/
1707-
public boolean isAssignableFrom(ResolvableType... types) {
1727+
public boolean isAssignableFrom(ResolvableType type, @Nullable Map<Type, Type> matchedBefore) {
17081728
for (ResolvableType bound : this.bounds) {
1709-
for (ResolvableType type : types) {
1710-
if (!isAssignable(bound, type)) {
1711-
return false;
1712-
}
1729+
if (this.kind == Kind.UPPER ? !bound.isAssignableFrom(type, false, matchedBefore, false) :
1730+
!type.isAssignableFrom(bound, false, matchedBefore, false)) {
1731+
return false;
17131732
}
17141733
}
17151734
return true;
17161735
}
17171736

1718-
private boolean isAssignable(ResolvableType source, ResolvableType from) {
1719-
return (this.kind == Kind.UPPER ? source.isAssignableFrom(from) : from.isAssignableFrom(source));
1737+
/**
1738+
* Return {@code true} if these bounds are assignable to the specified type.
1739+
* @param type the type to test against
1740+
* @return {@code true} if these bounds are assignable to the type
1741+
* @since 6.2
1742+
*/
1743+
public boolean isAssignableTo(ResolvableType type, @Nullable Map<Type, Type> matchedBefore) {
1744+
if (this.kind == Kind.UPPER) {
1745+
for (ResolvableType bound : this.bounds) {
1746+
if (type.isAssignableFrom(bound, false, matchedBefore, false)) {
1747+
return true;
1748+
}
1749+
}
1750+
return false;
1751+
}
1752+
else {
1753+
return (type.resolve() == Object.class);
1754+
}
17201755
}
17211756

17221757
/**
@@ -1728,21 +1763,30 @@ public ResolvableType[] getBounds() {
17281763

17291764
/**
17301765
* Get a {@link WildcardBounds} instance for the specified type, returning
1731-
* {@code null} if the specified type cannot be resolved to a {@link WildcardType}.
1766+
* {@code null} if the specified type cannot be resolved to a {@link WildcardType}
1767+
* or an equivalent unresolvable type variable.
17321768
* @param type the source type
17331769
* @return a {@link WildcardBounds} instance or {@code null}
17341770
*/
17351771
@Nullable
17361772
public static WildcardBounds get(ResolvableType type) {
1737-
ResolvableType resolveToWildcard = type;
1738-
while (!(resolveToWildcard.getType() instanceof WildcardType wildcardType)) {
1739-
if (resolveToWildcard == NONE) {
1773+
ResolvableType candidate = type;
1774+
while (!(candidate.getType() instanceof WildcardType || candidate.isUnresolvableTypeVariable())) {
1775+
if (candidate == NONE) {
17401776
return null;
17411777
}
1742-
resolveToWildcard = resolveToWildcard.resolveType();
1778+
candidate = candidate.resolveType();
1779+
}
1780+
Kind boundsType;
1781+
Type[] bounds;
1782+
if (candidate.getType() instanceof WildcardType wildcardType) {
1783+
boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER);
1784+
bounds = (boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds());
1785+
}
1786+
else {
1787+
boundsType = Kind.UPPER;
1788+
bounds = ((TypeVariable<?>) candidate.getType()).getBounds();
17431789
}
1744-
Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER);
1745-
Type[] bounds = (boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds());
17461790
ResolvableType[] resolvableBounds = new ResolvableType[bounds.length];
17471791
for (int i = 0; i < bounds.length; i++) {
17481792
resolvableBounds[i] = ResolvableType.forType(bounds[i], type.variableResolver);

spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,11 +1023,21 @@ void isAssignableFromForClassAndClass() {
10231023
@Test
10241024
void isAssignableFromCannotBeResolved() throws Exception {
10251025
ResolvableType objectType = ResolvableType.forClass(Object.class);
1026-
ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o"));
1026+
ResolvableType unresolvableVariable1 = ResolvableType.forField(AssignmentBase.class.getField("o"));
1027+
ResolvableType unresolvableVariable2 = ResolvableType.forField(AssignmentBase.class.getField("c"));
1028+
ResolvableType unresolvableVariable3 = ResolvableType.forField(AssignmentBase.class.getField("s"));
10271029

1028-
assertThat(unresolvableVariable.resolve()).isNull();
1029-
assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable);
1030-
assertThatResolvableType(unresolvableVariable).isAssignableFrom(objectType);
1030+
assertThat(unresolvableVariable1.resolve()).isNull();
1031+
assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable1);
1032+
assertThatResolvableType(unresolvableVariable1).isAssignableFrom(objectType);
1033+
1034+
assertThat(unresolvableVariable2.resolve()).isNull();
1035+
assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable2);
1036+
assertThatResolvableType(unresolvableVariable2).isAssignableFrom(objectType);
1037+
1038+
assertThat(unresolvableVariable3.resolve()).isEqualTo(Serializable.class);
1039+
assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable3);
1040+
assertThatResolvableType(unresolvableVariable3).isNotAssignableFrom(objectType);
10311041
}
10321042

10331043
@Test
@@ -1157,7 +1167,7 @@ void isAssignableFromForWildcards() throws Exception {
11571167

11581168
// T <= ? extends T
11591169
assertThatResolvableType(extendsCharSequence).isAssignableFrom(charSequence, string).isNotAssignableFrom(object);
1160-
assertThatResolvableType(charSequence).isNotAssignableFrom(extendsObject, extendsCharSequence, extendsString);
1170+
assertThatResolvableType(charSequence).isAssignableFrom(extendsCharSequence, extendsString).isNotAssignableFrom(extendsObject);
11611171
assertThatResolvableType(extendsAnon).isAssignableFrom(object, charSequence, string);
11621172

11631173
// T <= ? super T
@@ -1367,6 +1377,14 @@ void spr16456() throws Exception {
13671377
assertThat(type.resolveGeneric()).isEqualTo(Integer.class);
13681378
}
13691379

1380+
@Test
1381+
void gh22902() throws Exception {
1382+
ResolvableType ab = ResolvableType.forField(ABClient.class.getField("field"));
1383+
assertThat(ab.isAssignableFrom(Object.class)).isFalse();
1384+
assertThat(ab.isAssignableFrom(AwithB.class)).isTrue();
1385+
assertThat(ab.isAssignableFrom(AwithoutB.class)).isFalse();
1386+
}
1387+
13701388
@Test
13711389
void gh32327() throws Exception {
13721390
ResolvableType repository1 = ResolvableType.forField(Fields.class.getField("repository"));
@@ -1375,7 +1393,7 @@ void gh32327() throws Exception {
13751393
assertThat(repository1.hasUnresolvableGenerics()).isFalse();
13761394
assertThat(repository1.isAssignableFrom(repository2)).isFalse();
13771395
assertThat(repository1.isAssignableFromResolvedPart(repository2)).isTrue();
1378-
assertThat(repository1.isAssignableFrom(repository3)).isFalse();
1396+
assertThat(repository1.isAssignableFrom(repository3)).isTrue();
13791397
assertThat(repository1.isAssignableFromResolvedPart(repository3)).isTrue();
13801398
assertThat(repository2.hasUnresolvableGenerics()).isTrue();
13811399
assertThat(repository2.isAssignableFrom(repository1)).isTrue();
@@ -1520,7 +1538,7 @@ interface TypedMethods extends Methods<String> {
15201538
}
15211539

15221540

1523-
static class AssignmentBase<O, C, S> {
1541+
static class AssignmentBase<O, C, S extends Serializable> {
15241542

15251543
public O o;
15261544

@@ -1722,6 +1740,40 @@ public abstract class UnresolvedWithGenerics {
17221740
}
17231741

17241742

1743+
interface A {
1744+
1745+
void doA();
1746+
}
1747+
1748+
interface B {
1749+
1750+
void doB();
1751+
}
1752+
1753+
static class ABClient<T extends A & B> {
1754+
1755+
public T field;
1756+
}
1757+
1758+
static class AwithB implements A, B {
1759+
1760+
@Override
1761+
public void doA() {
1762+
}
1763+
1764+
@Override
1765+
public void doB() {
1766+
}
1767+
}
1768+
1769+
static class AwithoutB implements A {
1770+
1771+
@Override
1772+
public void doA() {
1773+
}
1774+
}
1775+
1776+
17251777
private static class ResolvableTypeAssert extends AbstractAssert<ResolvableTypeAssert, ResolvableType>{
17261778

17271779
public ResolvableTypeAssert(ResolvableType actual) {

0 commit comments

Comments
 (0)