Skip to content

Commit

Permalink
Consistently handle generics in TypeDescriptor.equals
Browse files Browse the repository at this point in the history
Properly processes recursive types through always comparing generics via the top-level ResolvableType (rather than through nested TypeDescriptors with custom ResolvableType instances).

Closes gh-33932
  • Loading branch information
jhoeller committed Dec 10, 2024
1 parent 3e3ca74 commit 7de1dc8
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

/**
* Contextual descriptor about a type to convert from or to.
Expand Down Expand Up @@ -501,16 +500,7 @@ public boolean equals(@Nullable Object other) {
if (!annotationsMatch(otherDesc)) {
return false;
}
if (isCollection() || isArray()) {
return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), otherDesc.getElementTypeDescriptor());
}
else if (isMap()) {
return (ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), otherDesc.getMapKeyTypeDescriptor()) &&
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor()));
}
else {
return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics());
}
return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics());
}

private boolean annotationsMatch(TypeDescriptor otherDesc) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,30 @@ void hasUnresolvableGenericsWithEnum() {
assertThat(type.hasUnresolvableGenerics()).isFalse();
}

@Test // gh-33932
void recursiveType() {
assertThat(ResolvableType.forClass(RecursiveMap.class)).isEqualTo(
ResolvableType.forClass(RecursiveMap.class));

ResolvableType resolvableType1 = ResolvableType.forClassWithGenerics(Map.class,
String.class, RecursiveMap.class);
ResolvableType resolvableType2 = ResolvableType.forClassWithGenerics(Map.class,
String.class, RecursiveMap.class);
assertThat(resolvableType1).isEqualTo(resolvableType2);
}

@Test // gh-33932
void recursiveTypeWithInterface() {
assertThat(ResolvableType.forClass(RecursiveMapWithInterface.class)).isEqualTo(
ResolvableType.forClass(RecursiveMapWithInterface.class));

ResolvableType resolvableType1 = ResolvableType.forClassWithGenerics(Map.class,
String.class, RecursiveMapWithInterface.class);
ResolvableType resolvableType2 = ResolvableType.forClassWithGenerics(Map.class,
String.class, RecursiveMapWithInterface.class);
assertThat(resolvableType1).isEqualTo(resolvableType2);
}

@Test
void spr11219() throws Exception {
ResolvableType type = ResolvableType.forField(BaseProvider.class.getField("stuff"), BaseProvider.class);
Expand Down Expand Up @@ -1836,6 +1860,16 @@ public void doA() {
}


@SuppressWarnings("serial")
static class RecursiveMap extends HashMap<String, RecursiveMap> {
}

@SuppressWarnings("serial")
static class RecursiveMapWithInterface extends HashMap<String, RecursiveMapWithInterface>
implements Map<String, RecursiveMapWithInterface> {
}


private static class ResolvableTypeAssert extends AbstractAssert<ResolvableTypeAssert, ResolvableType>{

public ResolvableTypeAssert(ResolvableType actual) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,30 @@ void equalityWithGenerics() {
assertThat(td1).isNotEqualTo(td2);
}

@Test // gh-33932
void recursiveType() {
assertThat(TypeDescriptor.valueOf(RecursiveMap.class)).isEqualTo(
TypeDescriptor.valueOf(RecursiveMap.class));

TypeDescriptor typeDescriptor1 = TypeDescriptor.map(Map.class,
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMap.class));
TypeDescriptor typeDescriptor2 = TypeDescriptor.map(Map.class,
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMap.class));
assertThat(typeDescriptor1).isEqualTo(typeDescriptor2);
}

@Test // gh-33932
void recursiveTypeWithInterface() {
assertThat(TypeDescriptor.valueOf(RecursiveMapWithInterface.class)).isEqualTo(
TypeDescriptor.valueOf(RecursiveMapWithInterface.class));

TypeDescriptor typeDescriptor1 = TypeDescriptor.map(Map.class,
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMapWithInterface.class));
TypeDescriptor typeDescriptor2 = TypeDescriptor.map(Map.class,
TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(RecursiveMapWithInterface.class));
assertThat(typeDescriptor1).isEqualTo(typeDescriptor2);
}


// Methods designed for test introspection

Expand Down Expand Up @@ -987,6 +1011,16 @@ public void setListProperty(List<Number> t) {
}


@SuppressWarnings("serial")
static class RecursiveMap extends HashMap<String, RecursiveMap> {
}

@SuppressWarnings("serial")
static class RecursiveMapWithInterface extends HashMap<String, RecursiveMapWithInterface>
implements Map<String, RecursiveMapWithInterface> {
}


// Annotations used on tested elements

@Target({ElementType.PARAMETER})
Expand Down

0 comments on commit 7de1dc8

Please sign in to comment.