Consistent Class and array matching with Class comparison shortcut
Closes gh-31487
This commit is contained in:
@@ -262,7 +262,9 @@ public class ResolvableType implements Serializable {
|
||||
* @see #isAssignableFrom(ResolvableType)
|
||||
*/
|
||||
public boolean isAssignableFrom(Class<?> other) {
|
||||
return isAssignableFrom(forClass(other), null);
|
||||
// As of 6.1: shortcut assignability check for top-level Class references
|
||||
return (this.type instanceof Class<?> clazz ? ClassUtils.isAssignable(clazz, other) :
|
||||
isAssignableFrom(forClass(other), false, null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,10 +279,10 @@ public class ResolvableType implements Serializable {
|
||||
* {@code ResolvableType}; {@code false} otherwise
|
||||
*/
|
||||
public boolean isAssignableFrom(ResolvableType other) {
|
||||
return isAssignableFrom(other, null);
|
||||
return isAssignableFrom(other, false, null);
|
||||
}
|
||||
|
||||
private boolean isAssignableFrom(ResolvableType other, @Nullable Map<Type, Type> matchedBefore) {
|
||||
private boolean isAssignableFrom(ResolvableType other, boolean strict, @Nullable Map<Type, Type> matchedBefore) {
|
||||
Assert.notNull(other, "ResolvableType must not be null");
|
||||
|
||||
// If we cannot resolve types, we are not assignable
|
||||
@@ -288,13 +290,21 @@ public class ResolvableType implements Serializable {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deal with array by delegating to the component type
|
||||
if (isArray()) {
|
||||
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType()));
|
||||
if (matchedBefore != null) {
|
||||
if (matchedBefore.get(this.type) == other.type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// As of 6.1: shortcut assignability check for top-level Class references
|
||||
if (this.type instanceof Class<?> clazz && other.type instanceof Class<?> otherClazz) {
|
||||
return (strict ? clazz.isAssignableFrom(otherClazz) : ClassUtils.isAssignable(clazz, otherClazz));
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedBefore != null && matchedBefore.get(this.type) == other.type) {
|
||||
return true;
|
||||
// Deal with array by delegating to the component type
|
||||
if (isArray()) {
|
||||
return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType(), true, matchedBefore));
|
||||
}
|
||||
|
||||
// Deal with wildcard bounds
|
||||
@@ -340,13 +350,15 @@ public class ResolvableType implements Serializable {
|
||||
}
|
||||
}
|
||||
if (ourResolved == null) {
|
||||
ourResolved = resolve(Object.class);
|
||||
ourResolved = toClass();
|
||||
}
|
||||
Class<?> otherResolved = other.toClass();
|
||||
|
||||
// We need an exact type match for generics
|
||||
// List<CharSequence> is not assignable from List<String>
|
||||
if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) {
|
||||
if (exactMatch ? !ourResolved.equals(otherResolved) :
|
||||
(strict ? !ourResolved.isAssignableFrom(otherResolved) :
|
||||
!ClassUtils.isAssignable(ourResolved, otherResolved))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -357,13 +369,15 @@ public class ResolvableType implements Serializable {
|
||||
if (ourGenerics.length != typeGenerics.length) {
|
||||
return false;
|
||||
}
|
||||
if (matchedBefore == null) {
|
||||
matchedBefore = new IdentityHashMap<>(1);
|
||||
}
|
||||
matchedBefore.put(this.type, other.type);
|
||||
for (int i = 0; i < ourGenerics.length; i++) {
|
||||
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
|
||||
return false;
|
||||
if (ourGenerics.length > 0) {
|
||||
if (matchedBefore == null) {
|
||||
matchedBefore = new IdentityHashMap<>(1);
|
||||
}
|
||||
matchedBefore.put(this.type, other.type);
|
||||
for (int i = 0; i < ourGenerics.length; i++) {
|
||||
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], true, matchedBefore)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,13 +697,20 @@ class ResolvableTypeTests {
|
||||
assertThat(type.getGeneric(0).as(Collection.class).getGeneric(0).as(Collection.class).resolve()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void intArrayNotAssignableToIntegerArray() throws Exception {
|
||||
ResolvableType integerArray = ResolvableType.forField(Fields.class.getField("integerArray"));
|
||||
ResolvableType intArray = ResolvableType.forField(Fields.class.getField("intArray"));
|
||||
assertThat(integerArray.isAssignableFrom(intArray)).isFalse();
|
||||
assertThat(intArray.isAssignableFrom(integerArray)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveBoundedTypeVariableResult() throws Exception {
|
||||
ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableResult"));
|
||||
assertThat(type.resolve()).isEqualTo(CharSequence.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void resolveBoundedTypeVariableWildcardResult() throws Exception {
|
||||
ResolvableType type = ResolvableType.forMethodReturnType(Methods.class.getMethod("boundedTypeVariableWildcardResult"));
|
||||
@@ -718,30 +725,26 @@ class ResolvableTypeTests {
|
||||
|
||||
@Test
|
||||
void resolveTypeVariableFromSimpleInterfaceType() {
|
||||
ResolvableType type = ResolvableType.forClass(
|
||||
MySimpleInterfaceType.class).as(MyInterfaceType.class);
|
||||
ResolvableType type = ResolvableType.forClass(MySimpleInterfaceType.class).as(MyInterfaceType.class);
|
||||
assertThat(type.resolveGeneric()).isEqualTo(String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveTypeVariableFromSimpleCollectionInterfaceType() {
|
||||
ResolvableType type = ResolvableType.forClass(
|
||||
MyCollectionInterfaceType.class).as(MyInterfaceType.class);
|
||||
ResolvableType type = ResolvableType.forClass(MyCollectionInterfaceType.class).as(MyInterfaceType.class);
|
||||
assertThat(type.resolveGeneric()).isEqualTo(Collection.class);
|
||||
assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveTypeVariableFromSimpleSuperclassType() {
|
||||
ResolvableType type = ResolvableType.forClass(
|
||||
MySimpleSuperclassType.class).as(MySuperclassType.class);
|
||||
ResolvableType type = ResolvableType.forClass(MySimpleSuperclassType.class).as(MySuperclassType.class);
|
||||
assertThat(type.resolveGeneric()).isEqualTo(String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveTypeVariableFromSimpleCollectionSuperclassType() {
|
||||
ResolvableType type = ResolvableType.forClass(
|
||||
MyCollectionSuperclassType.class).as(MySuperclassType.class);
|
||||
ResolvableType type = ResolvableType.forClass(MyCollectionSuperclassType.class).as(MySuperclassType.class);
|
||||
assertThat(type.resolveGeneric()).isEqualTo(Collection.class);
|
||||
assertThat(type.resolveGeneric(0, 0)).isEqualTo(String.class);
|
||||
}
|
||||
@@ -768,8 +771,7 @@ class ResolvableTypeTests {
|
||||
void resolveTypeVariableFromSuperType() throws Exception {
|
||||
ResolvableType type = ResolvableType.forClass(ExtendsList.class);
|
||||
assertThat(type.resolve()).isEqualTo(ExtendsList.class);
|
||||
assertThat(type.asCollection().resolveGeneric())
|
||||
.isEqualTo(CharSequence.class);
|
||||
assertThat(type.asCollection().resolveGeneric()).isEqualTo(CharSequence.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1021,6 +1023,7 @@ class ResolvableTypeTests {
|
||||
void isAssignableFromCannotBeResolved() throws Exception {
|
||||
ResolvableType objectType = ResolvableType.forClass(Object.class);
|
||||
ResolvableType unresolvableVariable = ResolvableType.forField(AssignmentBase.class.getField("o"));
|
||||
|
||||
assertThat(unresolvableVariable.resolve()).isNull();
|
||||
assertThatResolvableType(objectType).isAssignableFrom(unresolvableVariable);
|
||||
assertThatResolvableType(unresolvableVariable).isAssignableFrom(objectType);
|
||||
@@ -1294,7 +1297,7 @@ class ResolvableTypeTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasUnresolvableGenericsWhenImplementesRawInterface() throws Exception {
|
||||
void hasUnresolvableGenericsWhenImplementingRawInterface() throws Exception {
|
||||
ResolvableType type = ResolvableType.forClass(MySimpleInterfaceTypeWithImplementsRaw.class);
|
||||
for (ResolvableType generic : type.getGenerics()) {
|
||||
assertThat(generic.resolve()).isNotNull();
|
||||
@@ -1432,6 +1435,10 @@ class ResolvableTypeTests {
|
||||
public Map<Map<String, Integer>, Map<Byte, Long>> nested;
|
||||
|
||||
public T[] variableTypeGenericArray;
|
||||
|
||||
public Integer[] integerArray;
|
||||
|
||||
public int[] intArray;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user