frozenlib / test-strategy

Procedural macro to easily write higher-order strategies in proptest.
Apache License 2.0
44 stars 10 forks source link

Use param type to help Rust inference #5

Open RReverser opened 2 years ago

RReverser commented 2 years ago

Currently, if I try to use types like this:

#[proptest]
fn test_ra(#[strategy(0..=24)] h: u8, #[strategy(0..60)] m: u8, #[strategy(0..60)] s: u8) {

I get an error like:

error[E0271]: type mismatch resolving `<RangeInclusive<i32> as proptest::strategy::Strategy>::Value == u8`
   --> src\coords\astro.rs:279:27
    |
279 |     fn test_ra(#[strategy(0..=24)] h: u8, #[strategy(0..60)] m: u8, #[strategy(0..60)] s: u8) {
    |                           ^ expected `u8`, found `i32`
    |
note: required by a bound in `_strategy_of_h`
   --> src\coords\astro.rs:279:39
    |
278 |     #[proptest]
    |     ----------- required by a bound in this
279 |     fn test_ra(#[strategy(0..=24)] h: u8, #[strategy(0..60)] m: u8, #[strategy(0..60)] s: u8) {
    |                                       ^^ required by this bound in `_strategy_of_h`

So, just like when using strategies directly in proptest!, I have to add suffixes to clarify which type I meant and then it works:

#[proptest]
fn test_ra(#[strategy(0..=24_u8)] h: u8, #[strategy(0..60_u8)] m: u8, #[strategy(0..60_u8)] s: u8) {

However, test_strategy has more info that regular proptest! macro does - namely, it has access to explicit parameter types. Could it perhaps pass those through to corresponding generics to make inference work correctly without explicit suffixes?

frozenlib commented 2 years ago

test_strategy generates the following code to get the value of the type that implements Strategy<Value=u8>. This code causes that error because type inference does not work.

use proptest::strategy::Strategy;
use std::fmt::Debug;
fn _strategy_of_h<T: Debug, S: Strategy<Value = T>>(s: S) -> impl Strategy<Value = T> {
    s
}
let _s = _strategy_of_h::<u8, _>(0..=24);

If I could find some code where type inference works, I could fix it, but I don't know how to write such code?

If we cannot find a way to get type inference to work correctly, adding a suffix by the macro may be an option. However, this will work for simple cases such as 1..=24, but will not work for complex cases such as (1..=24).prop_map(|x| x * 2) because it cannot determine if adding a suffix is appropriate.

RReverser commented 2 years ago

I see. It does look like test-strategy already does what it can. Too bad that Strategy<Value = u8> is not enough for Rust to guess which Range<T> implementation to pick.

frozenlib commented 2 years ago

I wondered why it could be inferred for Iterator but not for Strategy. It seems to be due to the difference in whether the trait was implemented individually or generically.

The inference succeeded for Strategy1 in the following code but failed for Strategy2.

use std::ops::Range;

trait Strategy1 {
    type Value;
}
impl<T> Strategy1 for Range<T> {
    type Value = T;
}

trait Strategy2 {
    type Value;
}
impl Strategy2 for Range<u8> {
    type Value = u8;
}
impl Strategy2 for Range<i32> {
    type Value = i32;
}

fn s1(s: impl Strategy1<Value = u8>) {}
fn s2(s: impl Strategy2<Value = u8>) {}

s1(0..10);
s2(0..10); // error[E0271]: type mismatch resolving `<std::ops::Range<i32> as Strategy2>::Value == u8`