-
-
Notifications
You must be signed in to change notification settings - Fork 64
Domains inject parameterized values incorrectly #499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@SimY4 Interesting catch. Will look into it. |
OK. It's a bug, but sadly one I've tried to fix a couple of times already without seeing a straightforward solution. Maybe I'll find the time to reinvestigate. The problem is that type matching is sometimes too loose. And it's very hard to fix this without making other cases fail. In your case the following change would fix it, but you probably know that already: class MyDomain extends DomainContextBase {
@Provide
Arbitrary<Predicate<CharSequence>> myThingArbitrary() {
return Arbitraries.just(new MyThing());
}
} |
@jlink yeah, I figured. Not sure why it works for provided in test though. Is it just by chance? I get that it's probably related to MyThing not having any explicit type arguments. Should it find and compare only matching interfaces between requirements and provided definitions? I.e. I need Predicate I have MyThing which has Predicate but Number is not assignable from CharSeq so no. |
I assume it's due to search order, which is in parts coincidental :-/
I don't really understand what you mean, but if you'd use an explicity |
I guess the reason is that you probably want the following to work: @Property
boolean test(@ForAll Predicate<Number> p1, @ForAll Predicate<Number> p2, @ForAll Number n) {
return p1.or(p2).test(n) == (p1.test(n) || p2.test(n));
}
@Provide
Arbitrary<Predicate<Double>> myThingArbitrary() {
return ...;
} Technically speaking The proper Java code would be @Property
boolean test(@ForAll Predicate<? super Number> p1, @ForAll Predicate<? super Number> p2, @ForAll Number n) {
return p1.or(p2).test(n) == (p1.test(n) || p2.test(n));
}
@Provide
Arbitrary<Predicate<Double>> myThingArbitrary() {
return ...;
} In that case, Of course, you might argue that it would break existing tests that relied on somewhat working jqwik's loose type comparison. However, I would argue, well, they have chosen Java language, so they should follow its verbose use-site-type-variance, and they must declare type variance on the use site according to the rules of Java language. Unfortunately, Java does not have declaration-site type variance, so the workarounds could be: WDYT? |
@vlsi But in this case I don't see how variance is relevant since Number and CharSequence has no intersection which should be detectable: Number and CharSequence has no relation. I get an impression that the error happens because TypeRef can't examine generic parameters. For example this works fine: class MyThing<T> implements Predicate<T> {
@Override
public boolean test(T cs) {
return true;
}
}
class MyDomain extends DomainContextBase {
@Provide
Arbitrary<MyThing<CharSequence>> myThingArbitrary() {
return Arbitraries.just(new MyThing());
}
} So the solution here is to look for the matching generic interface in type hierarchy and compare their generic parameters. In this example:
So there should be a pre-step before comparing generic parameters that will look for the right generic interface in arbitrary type hierarchy. |
@jlink , I suggest the following: if (targetType.getRawType().isAssignableFrom(rawType)) {
// TODO: this is too loose, e.g. DefaultStringArbitrary can be assigned to Arbitrary<Integer>
// In order to solve that nested type arguments of this and targetType must be considered
if (allTypeArgumentsCanBeAssigned(this.getTypeArguments(), targetType.getTypeArguments())) {
return true;
} I suggest the following:
I believe it would handle all the cases above, however, it would requite harawata/typeparameterresolver dependency WDYT? |
#492 might be relevant as well |
Here's a sample test case for @Example
void isAssignableParameterized() throws NoSuchFieldException, NoSuchMethodException {
class LocalClass {
public void test(
Predicate<Double> predicateDouble,
Predicate<Number> predicateNumber,
Predicate<? super Number> predicateSuperNumber,
Predicate<? extends Number> predicateExtendsNumber
) {
}
}
Method method = LocalClass.class.getMethod("test", Predicate.class, Predicate.class, Predicate.class, Predicate.class);
List<MethodParameter> parameters = JqwikReflectionSupport.getMethodParameters(method, LocalClass.class);
TypeUsage predicateDouble = TypeUsageImpl.forParameter(parameters.get(0));
TypeUsage predicateNumber = TypeUsageImpl.forParameter(parameters.get(1));
TypeUsage predicateSuperNumber = TypeUsageImpl.forParameter(parameters.get(2));
TypeUsage predicateExtendsNumber = TypeUsageImpl.forParameter(parameters.get(3));
assertThat(predicateDouble.canBeAssignedTo(predicateNumber)).isFalse();
assertThat(predicateNumber.canBeAssignedTo(predicateDouble)).isFalse();
assertThat(predicateDouble.canBeAssignedTo(predicateSuperNumber)).isTrue();
assertThat(predicateNumber.canBeAssignedTo(predicateSuperNumber)).isTrue();
assertThat(predicateNumber.canBeAssignedTo(predicateExtendsNumber)).isFalse();
assertThat(predicateDouble.canBeAssignedTo(predicateExtendsNumber)).isFalse();
} |
IMO (and the opinion of the compiler) the assertions are wrong: assertThat(predicateDouble.canBeAssignedTo(predicateSuperNumber)).isTrue();
assertThat(predicateNumber.canBeAssignedTo(predicateSuperNumber)).isTrue();
assertThat(predicateNumber.canBeAssignedTo(predicateExtendsNumber)).isFalse();
assertThat(predicateDouble.canBeAssignedTo(predicateExtendsNumber)).isFalse(); should be assertThat(predicateDouble.canBeAssignedTo(predicateSuperNumber)).isFalse();
assertThat(predicateNumber.canBeAssignedTo(predicateSuperNumber)).isTrue();
assertThat(predicateNumber.canBeAssignedTo(predicateExtendsNumber)).isTrue();
assertThat(predicateDouble.canBeAssignedTo(predicateExtendsNumber)).isTrue(); |
there should be predicateSuperNumber.canBeAssignedTo(predicateDouble).isTrue as well |
Just one other thought and another reason to find and compare matching generic interfaces is: class MyThing<T> implements Predicate<CharSequence> { ... } In here MyThing is assignable to Predicate but the generic param has nothing to do with the Predicate type argument. |
@SimY4 , please read "1." in #499 (comment) It seems you missed it |
Does not look like that was the case: void test(
Predicate<Double> predicateDouble,
Predicate<Number> predicateNumber,
Predicate<? super Number> predicateSuperNumber,
Predicate<? extends Number> predicateExtendsNumber
) {
predicateSuperNumber = predicateNumber;
// predicateDouble = predicateSuperNumber; // does not compile
predicateExtendsNumber = predicateNumber;
predicateExtendsNumber = predicateDouble;
} |
I'm working on it on branch issue-449 Variance and type parameter matching seems fine so far, but I introduced a problem with recursive types on the way :-/ |
@jlink for the purposes of PBT framework do you need to solve this for such generic case? I feel like you can't possibly expect to summon instances of not fully resolved types. And if they are resolved then T should be concrete. Essentially you can give up exploring the hierarchy branch when you get the parameterised type. You can't compare T and U even if they have common supertype. Wildcards are ok though. |
It's not about solving all cases, but the following property - and others like it - worked before: @Property
<T extends Comparable<T>> boolean constrainedTypeVariable(@ForAll T aValue) {
return aValue != null;
} Now it fails with a SOF. That's not acceptable. |
Provided a fix and released it as "1.7.5-SNAPSHOT". @SimY4 Maybe you can try if your use case now works. |
@jlink I ran my test suite using snapshot release - all seem to be working fine here. |
Since a change to the behaviour of |
Here's one more example which fails with bf4577b @Example
void canBeAssignedToParametereized() throws NoSuchFieldException, NoSuchMethodException {
abstract class StrFunction<T extends Number> implements Function<CharSequence, T> {
};
class LocalClass {
public void test(
Function<? extends CharSequence, Integer> functionExtendsCsInteger,
Function<? extends CharSequence, Number> functionExtendsCsNumber,
StrFunction<Number> customNumber,
StrFunction<Integer> customInteger
) {
// Compilation fails if uncomment
// functionExtendsCsInteger = customNumber;
functionExtendsCsNumber = customNumber;
functionExtendsCsInteger = customInteger;
// functionExtendsCsNumber = customInteger;
}
}
Method method = LocalClass.class.getMethod("test", Function.class, Function.class, StrFunction.class, StrFunction.class);
List<MethodParameter> parameters = JqwikReflectionSupport.getMethodParameters(method, LocalClass.class);
TypeUsage functionExtendsCsInteger = TypeUsageImpl.forParameter(parameters.get(0));
TypeUsage functionExtendsCsNumber = TypeUsageImpl.forParameter(parameters.get(1));
TypeUsage customNumber = TypeUsageImpl.forParameter(parameters.get(2));
TypeUsage customInteger = TypeUsageImpl.forParameter(parameters.get(3));
assertThat(customNumber.canBeAssignedTo(functionExtendsCsInteger)).isFalse();
// FAILS in bf4577bc356bcdb583a9adc7c953254e06b1878f
assertThat(customNumber.canBeAssignedTo(functionExtendsCsNumber)).isTrue();
// FAILS in bf4577bc356bcdb583a9adc7c953254e06b1878f
assertThat(customInteger.canBeAssignedTo(functionExtendsCsInteger)).isTrue();
assertThat(customInteger.canBeAssignedTo(functionExtendsCsNumber)).isFalse();
} |
I'll see if that can easily be remedied. I assume not. The fix is an improvement but not a cure :-/ |
Working on a solution in branch https://github.com/jqwik-team/jqwik/tree/issue499 |
pull request to solve most/all of the variance and assignment issues Also available as 1.8.0-SNAPSHOT |
Testing Problem
The setup like this:
will produce:
but if you remove domain and provide
MyThing
right in test class - no problem.The text was updated successfully, but these errors were encountered: