Skip to content

Commit

Permalink
Merge pull request #949 from jqno/fix-bounded-wildcard-generics
Browse files Browse the repository at this point in the history
Fix bounded wildcard generics
  • Loading branch information
jqno committed Apr 3, 2024
2 parents 0134b86 + 435c75f commit c16e40e
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- IllegalArgumentException `argument type mismatch` when a field's type has a wildcard generic and the underlying type has a type bound (such as `T extends Serializable`). ([Issue 940](https://github.com/jqno/equalsverifier/issues/940))
- AbstractMethodError when a the `equals` method in a field's class calls an abstract method. ([Issue 938](https://github.com/jqno/equalsverifier/issues/938))

## [3.16] - 2024-03-22
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.Serializable;
import java.util.Objects;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
Expand Down Expand Up @@ -141,6 +142,11 @@ public void succeed_whenRecordValidatesInput_givenValidPrefabValues() {
.verify();
}

@Test
public void succeed_whenRecord() {
EqualsVerifier.forClass(WildcardGenericRecordContainer.class).verify();
}

record SimpleRecord(int i, String s) {}

private record PrivateSimpleRecord(int i, String s) {}
Expand Down Expand Up @@ -247,4 +253,8 @@ record ValidatingConstructorRecord(String s) {
}
}
}

record BoundedGenericRecord<T extends Serializable>(T t) {}

record WildcardGenericRecordContainer(BoundedGenericRecord<?> bgr) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ private TypeTag(Class<?> type, List<TypeTag> genericTypes) {
* @return The TypeTag for the given field.
*/
public static TypeTag of(Field field, TypeTag enclosingType) {
return resolve(field.getGenericType(), enclosingType, false);
return resolve(field.getGenericType(), field.getType(), enclosingType, false);
}

private static TypeTag resolve(
Type type,
Class<?> typeAsClass,
TypeTag enclosingType,
boolean shortCircuitRecursiveTypeBound
) {
Expand All @@ -62,24 +63,27 @@ private static TypeTag resolve(
if (type instanceof ParameterizedType) {
return processParameterizedType(
(ParameterizedType) type,
typeAsClass,
enclosingType,
nestedTags,
shortCircuitRecursiveTypeBound
);
}
if (type instanceof GenericArrayType) {
return processGenericArray((GenericArrayType) type, enclosingType);
return processGenericArray((GenericArrayType) type, typeAsClass, enclosingType);
}
if (type instanceof WildcardType) {
return processWildcard(
(WildcardType) type,
typeAsClass,
enclosingType,
shortCircuitRecursiveTypeBound
);
}
if (type instanceof TypeVariable) {
return processTypeVariable(
(TypeVariable<?>) type,
typeAsClass,
enclosingType,
shortCircuitRecursiveTypeBound
);
Expand All @@ -95,40 +99,57 @@ private static TypeTag processClass(Class<?> type, List<TypeTag> nestedTags) {

private static TypeTag processParameterizedType(
ParameterizedType type,
Class<?> typeAsClass,
TypeTag enclosingType,
List<TypeTag> nestedTags,
boolean shortCircuitRecursiveTypeBound
) {
Type[] typeArgs = type.getActualTypeArguments();
for (Type typeArg : typeArgs) {
nestedTags.add(resolve(typeArg, enclosingType, shortCircuitRecursiveTypeBound));
nestedTags.add(
resolve(typeArg, typeAsClass, enclosingType, shortCircuitRecursiveTypeBound)
);
}
return new TypeTag((Class<?>) type.getRawType(), nestedTags);
}

private static TypeTag processGenericArray(GenericArrayType type, TypeTag enclosingType) {
TypeTag tag = resolve(type.getGenericComponentType(), enclosingType, false);
private static TypeTag processGenericArray(
GenericArrayType type,
Class<?> typeAsClass,
TypeTag enclosingType
) {
TypeTag tag = resolve(type.getGenericComponentType(), typeAsClass, enclosingType, false);
String arrayTypeName = "[L" + tag.getType().getName() + ";";
Class<?> arrayType = classForName(arrayTypeName);
return new TypeTag(arrayType, tag.getGenericTypes());
}

private static TypeTag processWildcard(
WildcardType type,
Class<?> typeAsClass,
TypeTag enclosingType,
boolean shortCircuitRecursiveTypeBound
) {
for (Type b : type.getLowerBounds()) {
return resolve(b, enclosingType, shortCircuitRecursiveTypeBound);
return resolve(b, typeAsClass, enclosingType, shortCircuitRecursiveTypeBound);
}
for (Type b : type.getUpperBounds()) {
return resolve(b, enclosingType, shortCircuitRecursiveTypeBound);
TypeTag upper = resolve(b, typeAsClass, enclosingType, shortCircuitRecursiveTypeBound);
if (!Object.class.equals(upper.getType())) {
return upper;
}
}
for (TypeVariable<?> tv : typeAsClass.getTypeParameters()) {
for (Type b : tv.getBounds()) {
return resolve(b, typeAsClass, enclosingType, shortCircuitRecursiveTypeBound);
}
}
return new TypeTag(Object.class);
}

private static TypeTag processTypeVariable(
TypeVariable<?> type,
Class<?> typeAsClass,
TypeTag enclosingType,
boolean shortCircuitRecursiveTypeBound
) {
Expand All @@ -139,7 +160,7 @@ private static TypeTag processTypeVariable(
}
for (Type b : type.getBounds()) {
if (!shortCircuitRecursiveTypeBound) {
return resolve(b, enclosingType, true);
return resolve(b, typeAsClass, enclosingType, true);
}
}
return new TypeTag(Object.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ public void correctnessOfRecursiveBoundedWildcardTypeVariable() throws NoSuchFie
assertEquals(expected, actual);
}

@Test
public void correctnessOfWildcardFieldWithBoundedType() throws NoSuchFieldException {
Field field = WildcardBoundedTypeVariableContainer.class.getDeclaredField("wildcard");
TypeTag expected = new TypeTag(BoundedTypeVariable.class, new TypeTag(Point.class));
TypeTag actual = TypeTag.of(field, TypeTag.NULL);
assertEquals(expected, actual);
}

@SuppressWarnings("unused")
static class ContainerContainer {

Expand Down Expand Up @@ -162,4 +170,10 @@ static class RecursiveBoundedWildcardTypeVariable<T extends Comparable<? super T

private T fieldWithBoundedTypeVariable;
}

@SuppressWarnings("unused")
static class WildcardBoundedTypeVariableContainer<T extends Point> {

private BoundedTypeVariable<?> wildcard;
}
}

0 comments on commit c16e40e

Please sign in to comment.