How do you write Proptest Strategies for fixed size vectors?

help
#1

I’m transitioning some code to use nalgebra. I ran into issues while moving the Strategy for generating random Vector3 data. The end goal is to get prop test up and running for random Vector3 data.

I have the following code:

    pub fn st_vec3<T>(s: impl Strategy<Value = T> + Clone) -> impl Strategy<Value = Vector3<T>>
        where
            T: Arbitrary + Scalar,
        {
            (s.clone(), s.clone(), s.clone()).prop_map(|(x, y, z)| Vector3::new(x, y, z))
        }

This gives the following error:

    error[E0271]: type mismatch resolving `<[closure@src/render/ray.rs:51:52: 51:85] as std::ops::FnOnce<((T, T, T),)>>::Output == na::Matrix<T, na::U3, na::U1, <na::DefaultAllocator as na::allocator::Allocator<T, na::U3>>::Buffer>`
      --> src/render/ray.rs:47:63
       |
    47 |     pub fn st_vec3<T>(s: impl Strategy<Value = T> + Clone) -> impl Strategy<Value = Vector3<T>>
       |                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `na::ArrayStorage`, found associated type
       |
       = note: expected type `na::Matrix<_, _, _, na::ArrayStorage<T, na::U3, na::U1>>`
                  found type `na::Matrix<_, _, _, <na::DefaultAllocator as na::allocator::Allocator<T, na::U3>>::Buffer>`
       = note: required because of the requirements on the impl of `proptest::strategy::traits::Strategy` for `proptest::strategy::map::Map<(impl Strategy<Value = T> + Clone, impl Strategy<Value = T> + Clone, impl Strategy<Value = T> + Clone), [closure@src/render/ray.rs:51:52: 51:85]>`
       = note: the return type of a function must have a statically known size

    error: aborting due to previous error

It seems like the issues is that the storage types mismatch here. I haven’t been able to figure out much about why that is.

#2

Hi! I can reproduce this without Proptest:

struct TheMap<T2> {
    t2: T2
}

trait Foo {
    type Value;

    fn do_map<T2>(&self, f: impl Fn(Self::Value) -> T2) -> TheMap<T2> {
        unimplemented!()
    }
}

impl<T> Foo for TheMap<T> {
    type Value = T;
}

fn st_vec3<T: Scalar>(s: impl Foo<Value = T> + Clone) -> impl Foo<Value = Vector3<T>> {
    s.do_map(|x| Vector3::repeat(x))
}

This looks like the compiler bug illustrated by that comment: https://github.com/rust-lang/rust/issues/60414#issuecomment-488021357. This seems to be caused by the use of an impl-trait as the return type of st_vec3. It seems the associated type for the storage is not resolved completely inside of the impl-trait.

A workaround for this compiler bug could be to specify the storage type explicitly like so:

    use na::{Matrix, U1, U3, ArrayStorage};

    pub fn st_vec3<T>(s: impl Strategy<Value = T> + Clone) -> impl Strategy<Value = Matrix<T, U3, U1, ArrayStorage<T, U3, U1>>>
        where
            T: Arbitrary + Scalar,
        {
            (s.clone(), s.clone(), s.clone()).prop_map(|(x, y, z)| Vector3::new(x, y, z))
        }
#3

Yep, that was it. Manually specifying the type like this fixed the issue.
Thanks for the link to the comment on the compiler issue. I hadn’t found that.