Skip to content

Commit

Permalink
Fix type matching bug
Browse files Browse the repository at this point in the history
  • Loading branch information
jlink committed Jun 11, 2024
1 parent 3db8e5f commit 4d419a3
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 2 deletions.
6 changes: 6 additions & 0 deletions api/src/main/java/net/jqwik/api/providers/TypeUsage.java
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,10 @@ static TypeUsage forType(Type type) {
@API(status = EXPERIMENTAL, since = "1.6.0")
TypeUsage withMetaInfo(String key, Object value);


/**
* Check for type equality, ie do not consider annotations.
*/
@API(status = EXPERIMENTAL, since = "1.9.0")
boolean hasSameTypeAs(TypeUsage other);
}
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,8 @@ private boolean allTypeArgumentsCanBeAssigned(
return false;
}

// Unbound type variables can only be assigned to themselves.
if (targetTypeArgument.isTypeVariable() && !providedTypeArgument.equals(targetTypeArgument)) {
// Unbound type variables can only be assigned to themselves or type-equal type usages.
if (targetTypeArgument.isTypeVariable() && !providedTypeArgument.hasSameTypeAs(targetTypeArgument)) {
return false;
}

Expand Down Expand Up @@ -622,6 +622,57 @@ private TypeUsageImpl cloneWith(Consumer<TypeUsageImpl> updater) {
}
}

@Override
public boolean hasSameTypeAs(TypeUsage otherUsage) {
if (this == otherUsage) {
return true;
}

if (!(otherUsage instanceof TypeUsageImpl)) {
return false;
}

TypeUsageImpl other = (TypeUsageImpl) otherUsage;

// The rest is mostly a copy of the equals method but without annotation checking
// and with comparing component types using hasSameTypeAs

if (!other.getRawType().equals(getRawType()))
return false;
if (!(haveSameTypes(other.typeArguments, typeArguments)))
return false;
if (other.isWildcard() != isWildcard()) {
return false;
}
if (other.isTypeVariable() != isTypeVariable()) {
return false;
}
if (other.isWildcard() && isWildcard()) {
if (!(haveSameTypes(other.lowerBounds, lowerBounds)))
return false;
if (!(haveSameTypes(other.upperBounds, upperBounds)))
return false;
}
if (other.isTypeVariable() && isTypeVariable()) {
if (!other.typeVariable.equals(typeVariable))
return false;
return haveSameTypes(other.upperBounds, upperBounds);
}
return other.isNullable() == isNullable();
}

private static boolean haveSameTypes(List<TypeUsage> left, List<TypeUsage> right) {
if (left.size() != right.size()) {
return false;
}
for (int i = 0; i < left.size(); i++) {
if (!left.get(i).hasSameTypeAs(right.get(i))) {
return false;
}
}
return true;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
Expand Down
76 changes: 76 additions & 0 deletions engine/src/test/java/net/jqwik/api/providers/TypeUsageTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,82 @@ public <T> void test(
}
}

@Group
@Label("hasSameType(TypeUsage)")
class HasSameType {

@Example
void parameterizedType() throws NoSuchMethodException {

class LocalClass {
public void listWithAnnotation(List<@From String> list) {}
}

Method method = LocalClass.class.getMethod("listWithAnnotation", List.class);
MethodParameter parameter = JqwikReflectionSupport.getMethodParameters(method, LocalClass.class).get(0);
TypeUsage listWithAnnotation = TypeUsageImpl.forParameter(parameter);

TypeUsage listOfString = TypeUsage.of(List.class, TypeUsage.of(String.class));
assertThat(listOfString.hasSameTypeAs(listOfString)).isTrue();
assertThat(listOfString.hasSameTypeAs(listWithAnnotation)).isTrue();

TypeUsage listOfInt = TypeUsage.of(List.class, TypeUsage.of(Integer.class));
assertThat(listOfString.hasSameTypeAs(listOfInt)).isFalse();

}

@Example
void parameterizedTypeWithWildcard() throws NoSuchMethodException {

class LocalClass {
public <T> void listWithVariable(List<T> list) {}

public <T> void listWithAnnotatedVariable(List<@From T> list) {}

public <S> void listWithOtherVariable(List<S> list) {}
}

Method method = LocalClass.class.getMethod("listWithVariable", List.class);
MethodParameter parameter = JqwikReflectionSupport.getMethodParameters(method, LocalClass.class).get(0);
TypeUsage listWithVariable = TypeUsageImpl.forParameter(parameter);

method = LocalClass.class.getMethod("listWithAnnotatedVariable", List.class);
parameter = JqwikReflectionSupport.getMethodParameters(method, LocalClass.class).get(0);
TypeUsage listWithAnnotatedVariable = TypeUsageImpl.forParameter(parameter);

method = LocalClass.class.getMethod("listWithOtherVariable", List.class);
parameter = JqwikReflectionSupport.getMethodParameters(method, LocalClass.class).get(0);
TypeUsage listWithOtherVariable = TypeUsageImpl.forParameter(parameter);

assertThat(listWithVariable.hasSameTypeAs(listWithVariable)).isTrue();
assertThat(listWithVariable.hasSameTypeAs(listWithAnnotatedVariable)).isTrue();
assertThat(listWithVariable.hasSameTypeAs(listWithOtherVariable)).isFalse();
}

@Example
void parameterizedNestedType() throws NoSuchMethodException {

class LocalClass {
public void listWithAnnotation(List<List<@From String>> list) {}
}

Method method = LocalClass.class.getMethod("listWithAnnotation", List.class);
MethodParameter parameter = JqwikReflectionSupport.getMethodParameters(method, LocalClass.class).get(0);
TypeUsage listWithAnnotation = TypeUsageImpl.forParameter(parameter);

TypeUsage listOfListOfString = TypeUsage.of(
List.class,
TypeUsage.of(
List.class,
TypeUsage.of(String.class)
)
);
assertThat(listOfListOfString.hasSameTypeAs(listOfListOfString)).isTrue();
assertThat(listOfListOfString.hasSameTypeAs(listWithAnnotation)).isTrue();
}

}

@Group
class TypeUsageEnhancers {

Expand Down

0 comments on commit 4d419a3

Please sign in to comment.