beck_core/
gen.rs

1//! One type-directed value generator, used by three features.
2//!
3//! `docs/21-tests-in-beck-and-proof.md` §21.3 rule 5: "Stub return values, property-test inputs
4//! and `given` gaps are the same problem: produce an inhabitant of a known type. The compiler has
5//! the full type, including `newtype`s, unions and records, so it can derive:
6//!
7//! * a **canonical** inhabitant (first variant, empty collection, zero, `""`) for the don't-care
8//!   case;
9//! * an **arbitrary** one, with shrinking, for `property` blocks;
10//! * and it can refuse, with a diagnostic, for a type with no inhabitant it can construct —
11//!   `secret[T]` being the interesting one, since inventing a secret in a test is exactly the sort
12//!   of thing that should require somebody to type it out."
13//!
14//! "This is one generator, used by three features, and it is the piece to build first because
15//! §21.2's property tests need it too." It is built once, here.
16//!
17//! # Determinism
18//!
19//! The randomness is a counter-based splitmix, seeded from the test's name and the run index — not
20//! from a clock. §21.2: "**A flaky Beck test should be impossible**, and if one appears it is a
21//! compiler defect." A property test that fails on run 37 fails on run 37 again, on any machine, so
22//! the shrunk counterexample the report prints is one a person can reproduce by re-running the
23//! command they already ran.
24
25use std::collections::BTreeMap;
26use std::sync::Arc;
27
28use crate::core::{Fields, Value};
29use crate::pmap::PMap;
30use crate::ty::{Ty, TyDecl};
31
32/// A type the generator will not invent a value for, and why.
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Uninhabitable {
35    pub ty: String,
36    pub why: &'static str,
37}
38
39impl std::fmt::Display for Uninhabitable {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(f, "cannot invent a `{}`: {}", self.ty, self.why)
42    }
43}
44
45impl std::error::Error for Uninhabitable {}
46
47type Types = BTreeMap<Arc<str>, TyDecl>;
48
49/// A counter-based PRNG. Splitmix64, which is the whole algorithm and needs no state beyond a
50/// counter — so a value's generation depends on *where* it is asked for, and nothing else.
51#[derive(Clone, Debug)]
52pub struct Rng {
53    state: u64,
54}
55
56impl Rng {
57    /// Seed from a name and a run index. Two runs of the same suite generate the same values.
58    pub fn seeded(name: &str, run: u64) -> Rng {
59        let mut h: u64 = 0xcbf2_9ce4_8422_2325;
60        for b in name.as_bytes() {
61            h ^= *b as u64;
62            h = h.wrapping_mul(0x0000_0100_0000_01b3);
63        }
64        Rng {
65            state: h ^ run.wrapping_mul(0x9e37_79b9_7f4a_7c15),
66        }
67    }
68
69    pub fn next_u64(&mut self) -> u64 {
70        self.state = self.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
71        let mut z = self.state;
72        z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
73        z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
74        z ^ (z >> 31)
75    }
76
77    fn below(&mut self, n: usize) -> usize {
78        if n == 0 {
79            0
80        } else {
81            (self.next_u64() % n as u64) as usize
82        }
83    }
84}
85
86/// The don't-care inhabitant — §21.3 rule 1's "'any value' is the default, so it needs no
87/// expression".
88///
89/// Deterministic and *small*: the first variant, an empty collection, zero, `""`. Small matters
90/// because this is what a stub returns when nobody said otherwise, and a surprising default value
91/// is worse than an obvious one.
92pub fn canonical(ty: &Ty, types: &Types) -> Result<Value, Uninhabitable> {
93    build(ty, types, None, 0)
94}
95
96/// An arbitrary inhabitant, for a `property` block's parameters.
97pub fn arbitrary(ty: &Ty, types: &Types, rng: &mut Rng) -> Result<Value, Uninhabitable> {
98    build(ty, types, Some(rng), 0)
99}
100
101/// How deep a recursive type is allowed to nest before the generator falls back to the canonical
102/// inhabitant. Without it a `union Tree: Node(l: Tree, r: Tree)` would not terminate.
103const MAX_DEPTH: usize = 4;
104
105fn build(
106    ty: &Ty,
107    types: &Types,
108    mut rng: Option<&mut Rng>,
109    depth: usize,
110) -> Result<Value, Uninhabitable> {
111    let (name, args) = match ty {
112        Ty::Con(n, args) => (n.as_ref(), args.as_slice()),
113        // A type variable is not a type: a `property` parameter has to be written down, and
114        // §3.1's "mandatory annotations on public signatures" means one always is.
115        Ty::Var(_) => {
116            return Err(Uninhabitable {
117                ty: ty.to_string(),
118                why: "it is still a type variable — write the type down",
119            })
120        }
121        Ty::Fun(..) => {
122            return Err(Uninhabitable {
123                ty: ty.to_string(),
124                why: "a function is code, and the generator invents data",
125            })
126        }
127    };
128    // Past the depth limit, stop taking chances and take the smallest thing.
129    if depth >= MAX_DEPTH {
130        rng = None;
131    }
132    let mut rng = rng;
133
134    match name {
135        Ty::UNIT => return Ok(Value::Unit),
136        Ty::BOOL => {
137            return Ok(Value::Bool(match reborrow(&mut rng) {
138                Some(r) => r.next_u64() & 1 == 1,
139                None => false,
140            }))
141        }
142        Ty::INT => {
143            return Ok(Value::Int(match reborrow(&mut rng) {
144                // A small band around zero: the interesting integers in a program that counts
145                // things are 0, 1 and the boundary, not 2^61.
146                Some(r) => (r.next_u64() % 21) as i64 - 10,
147                None => 0,
148            }));
149        }
150        Ty::FLOAT => {
151            return Ok(Value::float(match reborrow(&mut rng) {
152                Some(r) => (r.next_u64() % 2001) as f64 / 100.0 - 10.0,
153                None => 0.0,
154            }))
155        }
156        Ty::STR => {
157            return Ok(Value::str_(match reborrow(&mut rng) {
158                Some(r) => WORDS[r.below(WORDS.len())],
159                None => "",
160            }))
161        }
162        // A view is not data (`value_to_repr` refuses one), so the generator refuses one too rather
163        // than inventing an empty page that would make an assertion pass for the wrong reason.
164        Ty::HTML | Ty::ATTR => {
165            return Err(Uninhabitable {
166                ty: ty.to_string(),
167                why: "a view is rendered from a state, not invented",
168            })
169        }
170        // §3.5's point, kept: "inventing a secret in a test is exactly the sort of thing that
171        // should require somebody to type it out".
172        Ty::SECRET => {
173            return Err(Uninhabitable {
174                ty: ty.to_string(),
175                why: "a secret has to be written out by a person, never invented by a generator",
176            })
177        }
178        Ty::LIST => {
179            let elem = args.first().cloned().unwrap_or_else(Ty::unit);
180            let n = match reborrow(&mut rng) {
181                Some(r) => r.below(4),
182                None => 0,
183            };
184            let mut out = Vec::with_capacity(n);
185            for i in 0..n {
186                out.push(build(&elem, types, reborrow(&mut rng), depth + 1 + i)?);
187            }
188            return Ok(Value::list(out));
189        }
190        Ty::MAP => {
191            let k = args.first().cloned().unwrap_or_else(Ty::unit);
192            let v = args.get(1).cloned().unwrap_or_else(Ty::unit);
193            let n = match reborrow(&mut rng) {
194                Some(r) => r.below(3),
195                None => 0,
196            };
197            let mut m = PMap::new();
198            for i in 0..n {
199                let key = build(&k, types, reborrow(&mut rng), depth + 1 + i)?;
200                let val = build(&v, types, reborrow(&mut rng), depth + 1 + i)?;
201                m = m.insert(key, val);
202            }
203            return Ok(Value::Map(m));
204        }
205        // A stream or a signal is a node in the graph, not a value a test hands anybody.
206        Ty::STREAM | Ty::SIGNAL => {
207            return Err(Uninhabitable {
208                ty: ty.to_string(),
209                why: "a signal is a node in the program's graph, not a value",
210            })
211        }
212        _ => {}
213    }
214
215    match types.get(name) {
216        Some(TyDecl::Newtype { inner, .. }) => {
217            let v = build(inner, types, rng, depth + 1)?;
218            Ok(Value::data(
219                Arc::from(name),
220                None,
221                Fields::from_iter([(Arc::from("value"), v)]),
222            ))
223        }
224        Some(TyDecl::Alias { ty: inner, .. }) => build(inner, types, rng, depth),
225        Some(TyDecl::Model { fields, .. }) => {
226            let fields = fields.clone();
227            let mut out = Fields::new();
228            for (i, (fname, fty)) in fields.iter().enumerate() {
229                let fty = crate::ty::instantiate_decl(fty, args);
230                out.insert(
231                    fname.clone(),
232                    build(&fty, types, reborrow(&mut rng), depth + 1 + i)?,
233                );
234            }
235            Ok(Value::data(Arc::from(name), None, out))
236        }
237        Some(TyDecl::Union { variants, .. }) => {
238            if variants.is_empty() {
239                return Err(Uninhabitable {
240                    ty: ty.to_string(),
241                    why: "it has no variants",
242                });
243            }
244            let variants = variants.clone();
245            // Prefer a variant that does not recurse into this same type, so the canonical
246            // inhabitant of a recursive union is a leaf.
247            let idx = match reborrow(&mut rng) {
248                Some(r) => r.below(variants.len()),
249                None => variants
250                    .iter()
251                    .position(|v| !v.fields.iter().any(|(_, t)| t.con_name() == Some(name)))
252                    .unwrap_or(0),
253            };
254            let v = &variants[idx];
255            let mut out = Fields::new();
256            for (i, (fname, fty)) in v.fields.iter().enumerate() {
257                let fty = crate::ty::instantiate_decl(fty, args);
258                out.insert(
259                    fname.clone(),
260                    build(&fty, types, reborrow(&mut rng), depth + 1 + i)?,
261                );
262            }
263            Ok(Value::data(Arc::from(name), Some(v.name.clone()), out))
264        }
265        _ => Err(Uninhabitable {
266            ty: ty.to_string(),
267            why: "this program does not declare it",
268        }),
269    }
270}
271
272fn reborrow<'a, 'b: 'a>(r: &'a mut Option<&'b mut Rng>) -> Option<&'a mut Rng> {
273    r.as_deref_mut()
274}
275
276/// Smaller candidates for a failing input, most-shrunk first.
277///
278/// Shrinking is a property of the *value*, not of the type: a shorter list is smaller than a longer
279/// one whatever the elements are, and an integer closer to zero is smaller than one further away.
280/// That keeps this total and terminating — every candidate is strictly smaller by
281/// [`size`], so a shrink loop cannot cycle.
282pub fn shrink(v: &Value) -> Vec<Value> {
283    let mut out = Vec::new();
284    match v {
285        Value::Int(0) | Value::Bool(false) | Value::Unit => {}
286        Value::Int(n) => {
287            out.push(Value::Int(0));
288            if n.abs() > 1 {
289                out.push(Value::Int(n / 2));
290            }
291            if *n > 0 {
292                out.push(Value::Int(n - 1));
293            } else {
294                out.push(Value::Int(n + 1));
295            }
296        }
297        Value::Bool(true) => out.push(Value::Bool(false)),
298        Value::Float(_) => {
299            if v.as_f64() != Some(0.0) {
300                out.push(Value::float(0.0));
301            }
302        }
303        Value::Str(s) if !s.is_empty() => {
304            out.push(Value::str_(""));
305            if s.len() > 1 {
306                out.push(Value::str_(&s[..s.len() / 2]));
307            }
308        }
309        Value::List(xs) if !xs.is_empty() => {
310            out.push(Value::list(Vec::new()));
311            if xs.len() > 1 {
312                out.push(Value::list(xs.slice(0, xs.len() / 2).to_vec()));
313                out.push(Value::list(xs.slice(1, xs.len()).to_vec()));
314            }
315            // …then one element at a time, so a failure caused by a *value* rather than by a
316            // length still shrinks.
317            for (i, x) in xs.iter().enumerate() {
318                for smaller in shrink(&x) {
319                    let mut copy = xs.to_vec();
320                    copy[i] = smaller;
321                    out.push(Value::list(copy));
322                }
323            }
324        }
325        Value::Map(m) if !m.is_empty() => {
326            out.push(Value::Map(PMap::new()));
327            if let Some((k, _)) = m.iter().next() {
328                out.push(Value::Map(m.remove(k)));
329            }
330        }
331        Value::Data(d) => {
332            for (name, f) in d.fields.iter() {
333                for smaller in shrink(f) {
334                    let mut copy = d.fields.clone();
335                    copy.insert(name.clone(), smaller);
336                    out.push(Value::data(d.ty.clone(), d.variant.clone(), copy));
337                }
338            }
339        }
340        _ => {}
341    }
342    let before = size(v);
343    out.retain(|c| size(c) < before);
344    out
345}
346
347/// A total order on "how big is this value", used to prove a shrink is progress.
348pub fn size(v: &Value) -> u64 {
349    match v {
350        Value::Unit => 0,
351        Value::Bool(b) => *b as u64,
352        Value::Int(n) => n.unsigned_abs(),
353        Value::Float(_) => v.as_f64().map(|f| f.abs() as u64).unwrap_or(0),
354        Value::Str(s) => s.len() as u64,
355        Value::List(xs) => {
356            let mut n = 1;
357            xs.for_each(|x| n += size(x));
358            n
359        }
360        Value::Map(m) => 1 + m.iter().map(|(k, val)| size(k) + size(val)).sum::<u64>(),
361        Value::Data(d) => d.fields.values().map(size).sum::<u64>(),
362        Value::Html(_) | Value::Attr(_) | Value::Closure(_) => 1,
363    }
364}
365
366/// The string pool. Short, memorable, and printable — a shrunk counterexample is something a person
367/// reads, and `"\u{1f4a9}\u{0}"` is a worse bug report than `"milk"`.
368const WORDS: &[&str] = &["", "a", "milk", "bread", " ", "ana", "bo", "x"];
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373    use crate::ty::Variant;
374
375    fn types() -> Types {
376        let mut t = crate::prelude::types();
377        t.insert(
378            Arc::from("Id"),
379            TyDecl::Newtype {
380                name: Arc::from("Id"),
381                params: Vec::new(),
382                inner: Ty::str_(),
383            },
384        );
385        t.insert(
386            Arc::from("Event"),
387            TyDecl::Union {
388                name: Arc::from("Event"),
389                params: Vec::new(),
390                variants: vec![
391                    Variant {
392                        name: Arc::from("Added"),
393                        fields: vec![(Arc::from("id"), Ty::con("Id"))],
394                    },
395                    Variant {
396                        name: Arc::from("Toggled"),
397                        fields: vec![(Arc::from("id"), Ty::con("Id"))],
398                    },
399                ],
400            },
401        );
402        t
403    }
404
405    #[test]
406    fn the_canonical_inhabitant_is_the_smallest_obvious_one() {
407        let t = types();
408        assert_eq!(canonical(&Ty::int(), &t).unwrap(), Value::Int(0));
409        assert_eq!(canonical(&Ty::str_(), &t).unwrap(), Value::str_(""));
410        assert_eq!(canonical(&Ty::bool_(), &t).unwrap(), Value::Bool(false));
411        assert_eq!(
412            canonical(&Ty::list(Ty::con("Event")), &t).unwrap(),
413            Value::list(Vec::new())
414        );
415        // First variant, and a newtype is transparent to the generator but not to the type system.
416        let e = canonical(&Ty::con("Event"), &t).unwrap();
417        assert_eq!(e.variant(), Some("Added"));
418        assert_eq!(e.field("id").unwrap().display(), "");
419    }
420
421    #[test]
422    fn a_secret_is_refused_because_somebody_has_to_type_it_out() {
423        let t = types();
424        let err = canonical(&Ty::secret(Ty::str_()), &t).expect_err("a secret is not invented");
425        assert!(err.why.contains("written out by a person"), "{err}");
426        // …and nesting does not launder it: a record holding one is refused too.
427        let mut t2 = t.clone();
428        t2.insert(
429            Arc::from("Creds"),
430            TyDecl::Model {
431                name: Arc::from("Creds"),
432                params: Vec::new(),
433                fields: vec![(Arc::from("key"), Ty::secret(Ty::str_()))],
434            },
435        );
436        assert!(canonical(&Ty::con("Creds"), &t2).is_err());
437    }
438
439    #[test]
440    fn generation_is_a_function_of_the_seed_and_nothing_else() {
441        let t = types();
442        let ty = Ty::list(Ty::con("Event"));
443        let a = arbitrary(&ty, &t, &mut Rng::seeded("a property", 7)).unwrap();
444        let b = arbitrary(&ty, &t, &mut Rng::seeded("a property", 7)).unwrap();
445        assert_eq!(a, b, "the same seed must produce the same value");
446        let c = arbitrary(&ty, &t, &mut Rng::seeded("a property", 8)).unwrap();
447        // Not asserting inequality of one pair — that is a property of the hash, not of the design.
448        // What is asserted is that the run index reaches the generator at all.
449        let d = arbitrary(&ty, &t, &mut Rng::seeded("a property", 8)).unwrap();
450        assert_eq!(c, d);
451    }
452
453    #[test]
454    fn a_recursive_union_terminates() {
455        let mut t = types();
456        t.insert(
457            Arc::from("Tree"),
458            TyDecl::Union {
459                name: Arc::from("Tree"),
460                params: Vec::new(),
461                variants: vec![
462                    Variant {
463                        name: Arc::from("Node"),
464                        fields: vec![
465                            (Arc::from("l"), Ty::con("Tree")),
466                            (Arc::from("r"), Ty::con("Tree")),
467                        ],
468                    },
469                    Variant {
470                        name: Arc::from("Leaf"),
471                        fields: vec![],
472                    },
473                ],
474            },
475        );
476        // The canonical inhabitant picks the non-recursive variant…
477        assert_eq!(
478            canonical(&Ty::con("Tree"), &t).unwrap().variant(),
479            Some("Leaf")
480        );
481        // …and an arbitrary one bottoms out at the depth limit rather than diverging.
482        for run in 0..20 {
483            arbitrary(&Ty::con("Tree"), &t, &mut Rng::seeded("t", run)).unwrap();
484        }
485    }
486
487    #[test]
488    fn every_shrink_is_strictly_smaller_so_the_loop_terminates() {
489        let t = types();
490        for run in 0..50 {
491            let v = arbitrary(&Ty::list(Ty::con("Event")), &t, &mut Rng::seeded("s", run)).unwrap();
492            for c in shrink(&v) {
493                assert!(size(&c) < size(&v), "{c:?} is not smaller than {v:?}");
494            }
495        }
496        assert!(shrink(&Value::Int(0)).is_empty());
497        assert!(shrink(&Value::list(Vec::new())).is_empty());
498    }
499
500    #[test]
501    fn a_type_parameter_reaches_the_field_it_stands_for() {
502        let t = types();
503        let v = canonical(&Ty::app(Ty::OPTION, vec![Ty::int()]), &t).unwrap();
504        // `Option`'s first variant is `Some(value: a)`, and `a` is `Int` here.
505        assert_eq!(v.variant(), Some("Some"));
506        assert_eq!(v.field("value"), Some(&Value::Int(0)));
507    }
508}