proptest/arbitrary/
functor.rs

1//-
2// Copyright 2017, 2018 The proptest developers
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! Provides higher order `Arbitrary` traits.
11//! This is mainly for use by `proptest_derive`.
12//!
13//! ## Stability note
14//!
15//! This trait is mainly defined for `proptest_derive` to simplify the
16//! mechanics of deriving recursive types. If you have custom containers
17//! and want to support recursive for those, it is a good idea to implement
18//! this trait.
19//!
20//! There are clearer and terser ways that work better with
21//! inference such as using `proptest::collection::vec(..)`
22//! to achieve the same result.
23//!
24//! For these reasons, the traits here are deliberately
25//! not exported in a convenient way.
26
27use crate::std_facade::fmt;
28
29use crate::strategy::{BoxedStrategy, Strategy};
30
31/// `ArbitraryF1` lets you lift a [`Strategy`] to unary
32/// type constructors such as `Box`, `Vec`, and `Option`.
33///
34/// The trait corresponds to
35/// [Haskell QuickCheck's `Arbitrary1` type class][HaskellQC].
36///
37/// [HaskellQC]:
38/// https://hackage.haskell.org/package/QuickCheck-2.10.1/docs/Test-QuickCheck-Arbitrary.html#t:Arbitrary1
39///
40/// [`Strategy`]: ../proptest/strategy/trait.Strategy.html
41pub trait ArbitraryF1<A: fmt::Debug>: fmt::Debug + Sized {
42    //==========================================================================
43    // Implementation note #1
44    //==========================================================================
45    // It might be better to do this with generic associated types by
46    // having an associated type:
47    //
48    // `type Strategy<A>: Strategy<Value = Self>;`
49    //
50    // But with this setup we will likely loose the ability to add bounds
51    // such as `Hash + Eq` on `A` which is needed for `HashSet`. We might
52    // be able to regain this ability with a ConstraintKinds feature.
53    //
54    // This alternate formulation will likely work better with type inference.
55    //
56    //==========================================================================
57    // Implementation note #2
58    //==========================================================================
59    //
60    // Until `-> impl Trait` has been stabilized, `BoxedStrategy` must be
61    // used. This incurs an unfortunate performance penalty - but since
62    // we are dealing with testing, it is better to provide slowed down and
63    // somewhat less general functionality than no functionality at all.
64    // Implementations should just use `.boxed()` in the end.
65    //==========================================================================
66
67    /// The type of parameters that [`lift1_with`] accepts for
68    /// configuration of the lifted and generated [`Strategy`]. Parameters
69    /// must implement [`Default`].
70    ///
71    /// [`lift1_with`]:
72    ///     trait.ArbitraryF1.html#tymethod.lift1_with
73    ///
74    /// [`Strategy`]: ../proptest/strategy/trait.Strategy.html
75    /// [`Default`]:
76    ///     https://doc.rust-lang.org/nightly/std/default/trait.Default.html
77    type Parameters: Default;
78
79    /// Lifts a given [`Strategy`] to a new [`Strategy`] for the (presumably)
80    /// bigger type. This is useful for lifting a `Strategy` for `SomeType`
81    /// to a container such as `Vec<SomeType>`.
82    ///
83    /// Calling this for the type `X` is the equivalent of using
84    /// [`X::lift1_with(base, Default::default())`].
85    ///
86    /// This method is defined in the trait for optimization for the
87    /// default if you want to do that. It is a logic error to not
88    /// preserve the semantics when overriding.
89    ///
90    /// [`Strategy`]: ../proptest/strategy/trait.Strategy.html
91    ///
92    /// [`X::lift1_with(base, Default::default())`]:
93    ///     trait.ArbitraryF1.html#tymethod.lift1_with
94    fn lift1<AS>(base: AS) -> BoxedStrategy<Self>
95    where
96        AS: Strategy<Value = A> + 'static,
97    {
98        Self::lift1_with(base, Self::Parameters::default())
99    }
100
101    /// Lifts a given [`Strategy`] to a new [`Strategy`] for the (presumably)
102    /// bigger type. This is useful for lifting a `Strategy` for `SomeType`
103    /// to a container such as `Vec` of `SomeType`. The composite strategy is
104    /// passed the arguments given in `args`.
105    ///
106    /// If you wish to use the [`default()`] arguments,
107    /// use [`lift1`] instead.
108    ///
109    /// [`Strategy`]: ../proptest/strategy/trait.Strategy.html
110    ///
111    /// [`lift1`]: trait.ArbitraryF1.html#method.lift1
112    ///
113    /// [`default()`]:
114    ///     https://doc.rust-lang.org/nightly/std/default/trait.Default.html
115    fn lift1_with<AS>(base: AS, args: Self::Parameters) -> BoxedStrategy<Self>
116    where
117        AS: Strategy<Value = A> + 'static;
118}
119
120/// `ArbitraryF2` lets you lift [`Strategy`] to binary
121/// type constructors such as `Result`, `HashMap`.
122///
123/// The trait corresponds to
124/// [Haskell QuickCheck's `Arbitrary2` type class][HaskellQC].
125///
126/// [HaskellQC]:
127/// https://hackage.haskell.org/package/QuickCheck-2.10.1/docs/Test-QuickCheck-Arbitrary.html#t:Arbitrary2
128///
129/// [`Strategy`]: ../proptest/strategy/trait.Strategy.html
130pub trait ArbitraryF2<A: fmt::Debug, B: fmt::Debug>:
131    fmt::Debug + Sized
132{
133    /// The type of parameters that [`lift2_with`] accepts for
134    /// configuration of the lifted and generated [`Strategy`]. Parameters
135    /// must implement [`Default`].
136    ///
137    /// [`lift2_with`]: trait.ArbitraryF2.html#tymethod.lift2_with
138    ///
139    /// [`Strategy`]: ../proptest/strategy/trait.Strategy.html
140    ///
141    /// [`Default`]:
142    ///     https://doc.rust-lang.org/nightly/std/default/trait.Default.html
143    type Parameters: Default;
144
145    /// Lifts two given strategies to a new [`Strategy`] for the (presumably)
146    /// bigger type. This is useful for lifting a `Strategy` for `Type1`
147    /// and one for `Type2` to a container such as `HashMap<Type1, Type2>`.
148    ///
149    /// Calling this for the type `X` is the equivalent of using
150    /// [`X::lift2_with(base, Default::default())`].
151    ///
152    /// This method is defined in the trait for optimization for the
153    /// default if you want to do that. It is a logic error to not
154    /// preserve the semantics when overriding.
155    ///
156    /// [`Strategy`]: ../proptest/strategy/trait.Strategy.html
157    ///
158    /// [`X::lift2_with(base, Default::default())`]:
159    ///     trait.Arbitrary.html#tymethod.lift2_with
160    fn lift2<AS, BS>(fst: AS, snd: BS) -> BoxedStrategy<Self>
161    where
162        AS: Strategy<Value = A> + 'static,
163        BS: Strategy<Value = B> + 'static,
164    {
165        Self::lift2_with(fst, snd, Self::Parameters::default())
166    }
167
168    /// Lifts two given strategies to a new [`Strategy`] for the (presumably)
169    /// bigger type. This is useful for lifting a `Strategy` for `Type1`
170    /// and one for `Type2` to a container such as `HashMap<Type1, Type2>`.
171    /// The composite strategy is passed the arguments given in `args`.
172    ///
173    /// If you wish to use the [`default()`] arguments,
174    /// use [`lift2`] instead.
175    ///
176    /// [`Strategy`]: ../proptest/strategy/trait.Strategy.html
177    ///
178    /// [`lift2`]: trait.ArbitraryF2.html#method.lift2
179    ///
180    /// [`default()`]:
181    ///     https://doc.rust-lang.org/nightly/std/default/trait.Default.html
182    fn lift2_with<AS, BS>(
183        fst: AS,
184        snd: BS,
185        args: Self::Parameters,
186    ) -> BoxedStrategy<Self>
187    where
188        AS: Strategy<Value = A> + 'static,
189        BS: Strategy<Value = B> + 'static;
190}
191
192macro_rules! lift1 {
193    ([$($bounds : tt)*] $typ: ty, $params: ty;
194     $base: ident, $args: ident => $logic: expr) => {
195        impl<A: ::core::fmt::Debug + $($bounds)*>
196        $crate::arbitrary::functor::ArbitraryF1<A>
197        for $typ {
198            type Parameters = $params;
199
200            fn lift1_with<S>($base: S, $args: Self::Parameters)
201                -> $crate::strategy::BoxedStrategy<Self>
202            where
203                S: $crate::strategy::Strategy<Value = A> + 'static
204            {
205                $crate::strategy::Strategy::boxed($logic)
206            }
207        }
208    };
209    ([$($bounds : tt)*] $typ: ty; $base: ident => $logic: expr) => {
210        lift1!([$($bounds)*] $typ, (); $base, _args => $logic);
211    };
212    ([$($bounds : tt)*] $typ: ty; $mapper: expr) => {
213        lift1!(['static + $($bounds)*] $typ; base =>
214            $crate::strategy::Strategy::prop_map(base, $mapper));
215    };
216    ([$($bounds : tt)*] $typ: ty) => {
217        lift1!(['static + $($bounds)*] $typ; base =>
218            $crate::strategy::Strategy::prop_map_into(base));
219    };
220}