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}