diff --git a/engine/src/main/java/net/jqwik/engine/support/GenericsClassContext.java b/engine/src/main/java/net/jqwik/engine/support/GenericsClassContext.java index d2f15a4df..eaedcfe71 100644 --- a/engine/src/main/java/net/jqwik/engine/support/GenericsClassContext.java +++ b/engine/src/main/java/net/jqwik/engine/support/GenericsClassContext.java @@ -120,17 +120,28 @@ private TypeResolution resolveVariableLocally(TypeResolution typeResolution) { } private TypeResolution resolveVariableInSupertypes(TypeResolution typeResolution) { - return supertypeContexts() - .map(context -> context.resolveVariableLocally(typeResolution)) - .filter(TypeResolution::typeHasChanged) + // TODO: Optimize so that only get supertypes of supertypes if necessary + // i.e. it cannot be resolved in direct supertypes + Set superTypes = getAllSupertypes(contextType); + List typeResolutionList = + superTypes.stream() + .map(GenericsSupport::contextFor) + .map(context -> context.resolveVariableLocally(typeResolution)) + .filter(TypeResolution::typeHasChanged) + .collect(Collectors.toList()); + + return typeResolutionList.stream() .findFirst() .orElse(typeResolution.unchanged()); } - private Stream supertypeContexts() { - Stream superclassStream = contextType.getSuperclass().map(Stream::of).orElseGet(Stream::empty); - Stream interfacesStream = contextType.getInterfaces().stream(); - return Stream.concat(superclassStream, interfacesStream).map(GenericsSupport::contextFor); + private Set getAllSupertypes(TypeUsage contextType) { + List directSupertypes = contextType.getSuperTypes(); + Set allSupertypes = new LinkedHashSet<>(directSupertypes); + for (TypeUsage directSupertype : directSupertypes) { + allSupertypes.addAll(getAllSupertypes(directSupertype)); + } + return allSupertypes; } private static class LookupTypeVariable { diff --git a/engine/src/test/java/examples/bugs/Issue519Bug.java b/engine/src/test/java/examples/bugs/Issue519Bug.java new file mode 100644 index 000000000..eea9dff55 --- /dev/null +++ b/engine/src/test/java/examples/bugs/Issue519Bug.java @@ -0,0 +1,45 @@ +package examples.bugs; + +import net.jqwik.api.*; + +// See https://github.com/jqwik-team/jqwik/issues/519 +class Issue519Bug implements C { + @Provide("anyT") + @Override + public Arbitrary anyT() { + return Arbitraries.of(new MyRecord()); + } +} + +interface A { + @Property + default boolean test1(@ForAll("anyT") T t) { + return true; + } + + Arbitrary anyT(); +} + +interface B> extends A { + @Property + default boolean test2(@ForAll("anyT") T t) { + return true; + } +} + +interface MyType extends Comparable { } + +interface C extends B { + @Property + default boolean test3(@ForAll("anyT") T t) { + return true; + } +} + +class MyRecord implements MyType { + @Override + public int compareTo(MyType o) { + return 0; + } +} + diff --git a/engine/src/test/java/net/jqwik/engine/support/GenericsSupportTests.java b/engine/src/test/java/net/jqwik/engine/support/GenericsSupportTests.java index 11a2a5be6..d8d6e8d4d 100644 --- a/engine/src/test/java/net/jqwik/engine/support/GenericsSupportTests.java +++ b/engine/src/test/java/net/jqwik/engine/support/GenericsSupportTests.java @@ -273,9 +273,43 @@ class Inner3 extends Inner2 {} assertThat(typeUsage.isOfType(GenericType.class)).isTrue(); assertThat(typeUsage.getTypeArguments()).hasSize(1); assertThat(typeUsage.getTypeArgument(0).isOfType(String.class)).isTrue(); + } + } + + @Example + @Label("Bug from issue#519") + void bug519() throws NoSuchMethodException { + abstract class A { + public boolean myMethod(T t) { + return true; + } + } + + abstract class B> extends A { + } + + abstract class MyType implements Comparable { } + abstract class C extends B { } + class MyRecord extends MyType { + @Override + public int compareTo(MyType o) { + return 0; + } + } + + class ConcreteC extends C { + } + + GenericsClassContext context = GenericsSupport.contextFor(ConcreteC.class); + Method method = A.class.getMethod("myMethod", Object.class); + + TypeResolution resolution = context.resolveParameter(method.getParameters()[0]); + TypeUsage typeUsage = TypeUsageImpl.forResolution(resolution); + + assertThat(typeUsage.isOfType(MyRecord.class)).isTrue(); } @Group