Skip to main content

hegel/generators/
generators.rs

1use crate::test_case::{TestCase, invalid_argument, labels};
2use std::marker::PhantomData;
3use std::sync::Arc;
4
5/// The core trait for all generators.
6///
7/// Generators produce values of type `T` by drawing from the engine through
8/// the [`TestCase`] passed to [`do_draw`](Self::do_draw).
9pub trait Generator<T> {
10    /// Produce a value.
11    #[doc(hidden)]
12    fn do_draw(&self, tc: &TestCase) -> T;
13
14    /// Transform generated values using a function.
15    ///
16    /// # Example
17    ///
18    /// ```no_run
19    /// use hegel::generators::{self as gs, Generator};
20    ///
21    /// // Generate even integers by doubling
22    /// let evens = gs::integers::<i32>().map(|n| n * 2);
23    /// ```
24    fn map<U, F>(self, f: F) -> Mapped<T, U, F, Self>
25    where
26        Self: Sized,
27        F: Fn(T) -> U + Send + Sync,
28    {
29        Mapped {
30            source: self,
31            f: Arc::new(f),
32            _phantom: PhantomData,
33        }
34    }
35
36    /// Generate a value, then use it to choose or configure another generator.
37    ///
38    /// # Example
39    ///
40    /// ```no_run
41    /// use hegel::generators::{self as gs, Generator};
42    ///
43    /// // Generate a length, then a vec of exactly that length
44    /// let generator = gs::integers::<usize>()
45    ///     .min_value(1)
46    ///     .max_value(10)
47    ///     .flat_map(|len| gs::vecs(gs::integers::<i32>())
48    ///         .min_size(len)
49    ///         .max_size(len));
50    /// ```
51    fn flat_map<U, G, F>(self, f: F) -> FlatMapped<T, U, G, F, Self>
52    where
53        Self: Sized,
54        G: Generator<U>,
55        F: Fn(T) -> G + Send + Sync,
56    {
57        FlatMapped {
58            source: self,
59            f,
60            _phantom: PhantomData,
61        }
62    }
63
64    /// Only keep generated values that satisfy the predicate.
65    ///
66    /// Retries up to 3 times, then calls `assume(false)` to reject the test case.
67    ///
68    /// # Example
69    ///
70    /// ```no_run
71    /// use hegel::generators::{self as gs, Generator};
72    ///
73    /// // Generate integers, then filter out the even ones
74    /// let odds = gs::integers::<i32>().filter(|n| n % 2 != 0);
75    /// ```
76    fn filter<F>(self, predicate: F) -> Filtered<T, F, Self>
77    where
78        Self: Sized,
79        F: Fn(&T) -> bool + Send + Sync,
80    {
81        Filtered {
82            source: self,
83            predicate,
84            enumerated: std::sync::OnceLock::new(),
85            _phantom: PhantomData,
86        }
87    }
88
89    /// Return all possible values if this generator has a known finite value set.
90    ///
91    /// Used by [`Filtered`]: instead of rejection sampling, enumerate the
92    /// valid elements and pick one directly. Mirrors Hypothesis's
93    /// `SampledFromStrategy.do_filtered_draw` optimization.
94    #[doc(hidden)]
95    fn enumerate_values(&self) -> Option<Vec<T>> {
96        None
97    }
98
99    /// Convert this generator into a type-erased boxed generator.
100    ///
101    /// This is needed when you have generators of different concrete types
102    /// but the same output type and need to store them together, e.g. in a
103    /// `Vec` or when passing to [`one_of()`](super::one_of).
104    ///
105    /// # Example
106    ///
107    /// ```no_run
108    /// use hegel::generators::{self as gs, Generator};
109    ///
110    /// // Different generator types producing the same output type —
111    /// // boxing lets them be stored in a vec and passed to one_of
112    /// let generator = vec![
113    ///     gs::integers::<i32>().min_value(0).max_value(10).boxed(),
114    ///     gs::integers::<i32>().map(|n| n * 100).boxed(),
115    ///     gs::sampled_from(vec![1, 2, 3]).boxed(),
116    /// ];
117    /// ```
118    fn boxed<'a>(self) -> BoxedGenerator<'a, T>
119    where
120        Self: Sized + Send + Sync + 'a,
121    {
122        BoxedGenerator {
123            inner: Arc::new(self),
124        }
125    }
126}
127
128impl<T, G: Generator<T>> Generator<T> for &G {
129    fn do_draw(&self, tc: &TestCase) -> T {
130        (*self).do_draw(tc)
131    }
132
133    fn enumerate_values(&self) -> Option<Vec<T>> {
134        (*self).enumerate_values()
135    }
136}
137
138/// Result of [`Generator::map`].
139pub struct Mapped<T, U, F, G> {
140    source: G,
141    f: Arc<F>,
142    _phantom: PhantomData<fn(T) -> U>,
143}
144
145impl<T, U, F, G> Generator<U> for Mapped<T, U, F, G>
146where
147    G: Generator<T>,
148    F: Fn(T) -> U + Send + Sync,
149{
150    fn do_draw(&self, tc: &TestCase) -> U {
151        tc.start_span(labels::MAPPED);
152        let result = (self.f)(self.source.do_draw(tc));
153        tc.stop_span(false);
154        result
155    }
156
157    fn enumerate_values(&self) -> Option<Vec<U>> {
158        self.source
159            .enumerate_values()
160            .map(|vals| vals.into_iter().map(|v| (self.f)(v)).collect())
161    }
162}
163
164/// Result of [`Generator::flat_map`].
165pub struct FlatMapped<T, U, G2, F, G1> {
166    source: G1,
167    f: F,
168    _phantom: PhantomData<fn(T) -> (U, G2)>,
169}
170
171impl<T, U, G2, F, G1> Generator<U> for FlatMapped<T, U, G2, F, G1>
172where
173    G1: Generator<T>,
174    G2: Generator<U>,
175    F: Fn(T) -> G2 + Send + Sync,
176{
177    fn do_draw(&self, tc: &TestCase) -> U {
178        tc.start_span(labels::FLAT_MAP);
179        let intermediate = self.source.do_draw(tc);
180        let next_gen = (self.f)(intermediate);
181        let result = next_gen.do_draw(tc);
182        tc.stop_span(false);
183        result
184    }
185}
186
187/// Result of [`Generator::filter`].
188pub struct Filtered<T, F, G> {
189    source: G,
190    predicate: F,
191    /// The source's enumerated values with the predicate applied, computed
192    /// once: for an enumerable source like `sampled_from`, re-enumerating
193    /// (which clones the whole element vector) on every draw is the dominant
194    /// cost of a filtered draw.
195    enumerated: std::sync::OnceLock<Option<Vec<T>>>,
196    _phantom: PhantomData<fn() -> T>,
197}
198
199impl<T, F, G> Filtered<T, F, G>
200where
201    T: Clone + Send + Sync,
202    G: Generator<T>,
203    F: Fn(&T) -> bool + Send + Sync,
204{
205    fn enumerated(&self) -> &Option<Vec<T>> {
206        self.enumerated.get_or_init(|| {
207            self.source
208                .enumerate_values()
209                .map(|vals| vals.into_iter().filter(|v| (self.predicate)(v)).collect())
210        })
211    }
212}
213
214impl<T, F, G> Generator<T> for Filtered<T, F, G>
215where
216    T: Clone + Send + Sync,
217    G: Generator<T>,
218    F: Fn(&T) -> bool + Send + Sync,
219{
220    fn do_draw(&self, tc: &TestCase) -> T {
221        if let Some(valid) = self.enumerated() {
222            if valid.is_empty() {
223                invalid_argument!(
224                    "Unsatisfiable filter: all values from the source generator \
225                     are rejected by the filter predicate"
226                );
227            }
228            let index = tc.generate_integer_i64(0, valid.len() as i64 - 1) as usize;
229            return valid[index].clone();
230        }
231        for _ in 0..3 {
232            tc.start_span(labels::FILTER);
233            let value = self.source.do_draw(tc);
234            if (self.predicate)(&value) {
235                tc.stop_span(false);
236                return value;
237            }
238            tc.stop_span(true);
239        }
240        tc.assume(false);
241        unreachable!()
242    }
243
244    fn enumerate_values(&self) -> Option<Vec<T>> {
245        self.enumerated().clone()
246    }
247}
248
249/// A type-erased generator with a lifetime parameter.
250pub struct BoxedGenerator<'a, T> {
251    pub(super) inner: Arc<dyn Generator<T> + Send + Sync + 'a>,
252}
253
254impl<T> Clone for BoxedGenerator<'_, T> {
255    fn clone(&self) -> Self {
256        BoxedGenerator {
257            inner: Arc::clone(&self.inner),
258        }
259    }
260}
261
262impl<T> Generator<T> for BoxedGenerator<'_, T> {
263    fn do_draw(&self, tc: &TestCase) -> T {
264        self.inner.do_draw(tc)
265    }
266
267    fn enumerate_values(&self) -> Option<Vec<T>> {
268        self.inner.enumerate_values()
269    }
270
271    fn boxed<'b>(self) -> BoxedGenerator<'b, T>
272    where
273        Self: Sized + Send + Sync + 'b,
274    {
275        BoxedGenerator { inner: self.inner }
276    }
277}