Skip to content

Commit 795ce16

Browse files
committed
Consistent Class and array matching with Class comparison shortcut
Closes gh-31487
1 parent b6e0b8c commit 795ce16

File tree

2 files changed

+50
-29
lines changed

2 files changed

+50
-29
lines changed

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

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,9 @@ public boolean isInstance(@Nullable Object obj) {
262262
* @see #isAssignableFrom(ResolvableType)
263263
*/
264264
public boolean isAssignableFrom(Class<?> other) {
265-
return isAssignableFrom(forClass(other), null);
265+
// As of 6.1: shortcut assignability check for top-level Class references
266+
return (this.type instanceof Class<?> clazz ? ClassUtils.isAssignable(clazz, other) :
267+
isAssignableFrom(forClass(other), false, null));
266268
}
267269

268270
/**
@@ -277,24 +279,32 @@ public boolean isAssignableFrom(Class<?> other) {
277279
* {@code ResolvableType}; {@code false} otherwise
278280
*/
279281
public boolean isAssignableFrom(ResolvableType other) {
280-
return isAssignableFrom(other, null);
282+
return isAssignableFrom(other, false, null);
281283
}
282284

283-
private boolean isAssignableFrom(ResolvableType other, @Nullable Map<Type, Type> matchedBefore) {
285+
private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable Map<Type, Type> matchedBefore) {
284286
Assert.notNull(other, "ResolvableType must not be null");
285287

286288
// If we cannot resolve types, we are not assignable
287289
if (this == NONE || other == NONE) {
288290
return false;
289291
}
290292

291-
// Deal with array by delegating to the component type
292-
if (isArray()) {
293-
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType()));
293+
if (matchedBefore != null) {
294+
if (matchedBefore.get(this.type) == other.type) {
295+
return true;
296+
}
297+
}
298+
else {
299+
// As of 6.1: shortcut assignability check for top-level Class references
300+
if (this.type instanceof Class<?> clazz && other.type instanceof Class<?> otherClazz) {
301+
return (strict ? clazz.isAssignableFrom(otherClazz) : ClassUtils.isAssignable(clazz, otherClazz));
302+
}
294303
}
295304

296-
if (matchedBefore != null && matchedBefore.get(this.type) == other.type) {
297-
return true;
305+
// Deal with array by delegating to the component type
306+
if (isArray()) {
307+
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType(), true, matchedBefore));
298308
}
299309

300310
// Deal with wildcard bounds
@@ -340,13 +350,15 @@ private boolean isAssignableFrom(ResolvableType other, @Nullable Map<Type, Type>
340350
}
341351
}
342352
if (ourResolved == null) {
343-
ourResolved = resolve(Object.class);
353+
ourResolved = toClass();
344354
}
345355
Class<?> otherResolved = other.toClass();
346356

347357
// We need an exact type match for generics
348358
// List<CharSequence> is not assignable from List<String>
349-
if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) {
359+
if (exactMatch ? !ourResolved.equals(otherResolved) :
360+
(strict ? !ourResolved.isAssignableFrom(otherResolved) :
361+
!ClassUtils.isAssignable(ourResolved, otherResolved))) {
350362
return false;
351363
}
352364

@@ -357,13 +369,15 @@ private boolean isAssignableFrom(ResolvableType other, @Nullable Map<Type, Type>
357369
if (ourGenerics.length != typeGenerics.length) {
358370
return false;
359371
}
360-
if (matchedBefore == null) {
361-
matchedBefore = new IdentityHashMap<>(1);
362-
}
363-
matchedBefore.put(this.type, other.type);
364-
for (int i = 0; i < ourGenerics.length; i++) {
365-
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
366-
return false;
372+
if (ourGenerics.length > 0) {
373+
if (matchedBefore == null) {
374+
matchedBefore = new IdentityHashMap<>(1);
375+
}
376+
matchedBefore.put(this.type, other.type);
377+
for (int i = 0; i < ourGenerics.length; i++) {
378+
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore)) {
379+
return false;
380+
}
367381
}
368382
}
369383
}

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

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -697,13 +697,20 @@ void doesResolveFromOuterOwner() throws Exception {
697697
assertThat(type.getGeneric(0).as(Collection.class).getGeneric(0).as(Collection.class).resolve()).isNull();
698698
}
699699

700+
@Test
701+
void intArrayNotAssignableToIntegerArray() throws Exception {
702+
ResolvableType integerArray = ResolvableType.forField(Fields.class.getField("integerArray"));
703+
ResolvableType intArray = ResolvableType.forField(Fields.class.getField("intArray"));
704+
assertThat(integerArray.isAssignableFrom(intArray)).isFalse();
705+
assertThat(intArray.isAssignableFrom(integerArray)).isFalse();
706+
}
707+
700708
@Test
701709
void resolveBoundedTypeVariableResult() throws Exception {
702710
ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableResult"));
703711
assertThat(type.resolve()).isEqualTo(CharSequence.class);
704712
}
705713

706-
707714
@Test
708715
void resolveBoundedTypeVariableWildcardResult() throws Exception {
709716
ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableWildcardResult"));
@@ -718,30 +725,26 @@ void resolveVariableNotFound() throws Exception {
718725

719726
@Test
720727
void resolveTypeVariableFromSimpleInterfaceType() {
721-
ResolvableType type = ResolvableType.forClass(
722-
MySimpleInterfaceType.class).as(MyInterfaceType.class);
728+
ResolvableType type = ResolvableType.forClass(MySimpleInterfaceType.class).as(MyInterfaceType.class);
723729
assertThat(type.resolveGeneric()).isEqualTo(String.class);
724730
}
725731

726732
@Test
727733
void resolveTypeVariableFromSimpleCollectionInterfaceType() {
728-
ResolvableType type = ResolvableType.forClass(
729-
MyCollectionInterfaceType.class).as(MyInterfaceType.class);
734+
ResolvableType type = ResolvableType.forClass(MyCollectionInterfaceType.class).as(MyInterfaceType.class);
730735
assertThat(type.resolveGeneric()).isEqualTo(Collection.class);
731736
assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class);
732737
}
733738

734739
@Test
735740
void resolveTypeVariableFromSimpleSuperclassType() {
736-
ResolvableType type = ResolvableType.forClass(
737-
MySimpleSuperclassType.class).as(MySuperclassType.class);
741+
ResolvableType type = ResolvableType.forClass(MySimpleSuperclassType.class).as(MySuperclassType.class);
738742
assertThat(type.resolveGeneric()).isEqualTo(String.class);
739743
}
740744

741745
@Test
742746
void resolveTypeVariableFromSimpleCollectionSuperclassType() {
743-
ResolvableType type = ResolvableType.forClass(
744-
MyCollectionSuperclassType.class).as(MySuperclassType.class);
747+
ResolvableType type = ResolvableType.forClass(MyCollectionSuperclassType.class).as(MySuperclassType.class);
745748
assertThat(type.resolveGeneric()).isEqualTo(Collection.class);
746749
assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class);
747750
}
@@ -768,8 +771,7 @@ void resolveTypeVariableFromFieldTypeWithImplementsType() throws Exception {
768771
void resolveTypeVariableFromSuperType() throws Exception {
769772
ResolvableType type = ResolvableType.forClass(ExtendsList.class);
770773
assertThat(type.resolve()).isEqualTo(ExtendsList.class);
771-
assertThat(type.asCollection().resolveGeneric())
772-
.isEqualTo(CharSequence.class);
774+
assertThat(type.asCollection().resolveGeneric()).isEqualTo(CharSequence.class);
773775
}
774776

775777
@Test
@@ -1021,6 +1023,7 @@ void isAssignableFromForClassAndClass() throws Exception {
10211023
void isAssignableFromCannotBeResolved() throws Exception {
10221024
ResolvableType objectType = ResolvableType.forClass(Object.class);
10231025
ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o"));
1026+
10241027
assertThat(unresolvableVariable.resolve()).isNull();
10251028
assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable);
10261029
assertThatResolvableType(unresolvableVariable).isAssignableFrom(objectType);
@@ -1294,7 +1297,7 @@ void hasUnresolvableGenericsWhenSelfNotResolvable() throws Exception {
12941297
}
12951298

12961299
@Test
1297-
void hasUnresolvableGenericsWhenImplementesRawInterface() throws Exception {
1300+
void hasUnresolvableGenericsWhenImplementingRawInterface() throws Exception {
12981301
ResolvableType type = ResolvableType.forClass(MySimpleInterfaceTypeWithImplementsRaw.class);
12991302
for (ResolvableType generic : type.getGenerics()) {
13001303
assertThat(generic.resolve()).isNotNull();
@@ -1432,6 +1435,10 @@ static class Fields<T> {
14321435
public Map<Map<String, Integer>, Map<Byte, Long>> nested;
14331436

14341437
public T[] variableTypeGenericArray;
1438+
1439+
public Integer[] integerArray;
1440+
1441+
public int[] intArray;
14351442
}
14361443

14371444

0 commit comments

Comments
 (0)