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(Arc::new(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(Arc::new(Vec::new())));
311            if xs.len() > 1 {
312                out.push(Value::List(Arc::new(xs[..xs.len() / 2].to_vec())));
313                out.push(Value::List(Arc::new(xs[1..].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.as_ref().clone();
320                    copy[i] = smaller;
321                    out.push(Value::List(Arc::new(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) => 1 + xs.iter().map(size).sum::<u64>(),
356        Value::Map(m) => 1 + m.iter().map(|(k, val)| size(k) + size(val)).sum::<u64>(),
357        Value::Data(d) => d.fields.values().map(size).sum::<u64>(),
358        Value::Html(_) | Value::Attr(_) | Value::Closure(_) => 1,
359    }
360}
361
362/// The string pool. Short, memorable, and printable — a shrunk counterexample is something a person
363/// reads, and `"\u{1f4a9}\u{0}"` is a worse bug report than `"milk"`.
364const WORDS: &[&str] = &["", "a", "milk", "bread", " ", "ana", "bo", "x"];
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use crate::ty::Variant;
370
371    fn types() -> Types {
372        let mut t = crate::prelude::types();
373        t.insert(
374            Arc::from("Id"),
375            TyDecl::Newtype {
376                name: Arc::from("Id"),
377                params: Vec::new(),
378                inner: Ty::str_(),
379            },
380        );
381        t.insert(
382            Arc::from("Event"),
383            TyDecl::Union {
384                name: Arc::from("Event"),
385                params: Vec::new(),
386                variants: vec![
387                    Variant {
388                        name: Arc::from("Added"),
389                        fields: vec![(Arc::from("id"), Ty::con("Id"))],
390                    },
391                    Variant {
392                        name: Arc::from("Toggled"),
393                        fields: vec![(Arc::from("id"), Ty::con("Id"))],
394                    },
395                ],
396            },
397        );
398        t
399    }
400
401    #[test]
402    fn the_canonical_inhabitant_is_the_smallest_obvious_one() {
403        let t = types();
404        assert_eq!(canonical(&Ty::int(), &t).unwrap(), Value::Int(0));
405        assert_eq!(canonical(&Ty::str_(), &t).unwrap(), Value::str_(""));
406        assert_eq!(canonical(&Ty::bool_(), &t).unwrap(), Value::Bool(false));
407        assert_eq!(
408            canonical(&Ty::list(Ty::con("Event")), &t).unwrap(),
409            Value::List(Arc::new(Vec::new()))
410        );
411        // First variant, and a newtype is transparent to the generator but not to the type system.
412        let e = canonical(&Ty::con("Event"), &t).unwrap();
413        assert_eq!(e.variant(), Some("Added"));
414        assert_eq!(e.field("id").unwrap().display(), "");
415    }
416
417    #[test]
418    fn a_secret_is_refused_because_somebody_has_to_type_it_out() {
419        let t = types();
420        let err = canonical(&Ty::secret(Ty::str_()), &t).expect_err("a secret is not invented");
421        assert!(err.why.contains("written out by a person"), "{err}");
422        // …and nesting does not launder it: a record holding one is refused too.
423        let mut t2 = t.clone();
424        t2.insert(
425            Arc::from("Creds"),
426            TyDecl::Model {
427                name: Arc::from("Creds"),
428                params: Vec::new(),
429                fields: vec![(Arc::from("key"), Ty::secret(Ty::str_()))],
430            },
431        );
432        assert!(canonical(&Ty::con("Creds"), &t2).is_err());
433    }
434
435    #[test]
436    fn generation_is_a_function_of_the_seed_and_nothing_else() {
437        let t = types();
438        let ty = Ty::list(Ty::con("Event"));
439        let a = arbitrary(&ty, &t, &mut Rng::seeded("a property", 7)).unwrap();
440        let b = arbitrary(&ty, &t, &mut Rng::seeded("a property", 7)).unwrap();
441        assert_eq!(a, b, "the same seed must produce the same value");
442        let c = arbitrary(&ty, &t, &mut Rng::seeded("a property", 8)).unwrap();
443        // Not asserting inequality of one pair — that is a property of the hash, not of the design.
444        // What is asserted is that the run index reaches the generator at all.
445        let d = arbitrary(&ty, &t, &mut Rng::seeded("a property", 8)).unwrap();
446        assert_eq!(c, d);
447    }
448
449    #[test]
450    fn a_recursive_union_terminates() {
451        let mut t = types();
452        t.insert(
453            Arc::from("Tree"),
454            TyDecl::Union {
455                name: Arc::from("Tree"),
456                params: Vec::new(),
457                variants: vec![
458                    Variant {
459                        name: Arc::from("Node"),
460                        fields: vec![
461                            (Arc::from("l"), Ty::con("Tree")),
462                            (Arc::from("r"), Ty::con("Tree")),
463                        ],
464                    },
465                    Variant {
466                        name: Arc::from("Leaf"),
467                        fields: vec![],
468                    },
469                ],
470            },
471        );
472        // The canonical inhabitant picks the non-recursive variant…
473        assert_eq!(
474            canonical(&Ty::con("Tree"), &t).unwrap().variant(),
475            Some("Leaf")
476        );
477        // …and an arbitrary one bottoms out at the depth limit rather than diverging.
478        for run in 0..20 {
479            arbitrary(&Ty::con("Tree"), &t, &mut Rng::seeded("t", run)).unwrap();
480        }
481    }
482
483    #[test]
484    fn every_shrink_is_strictly_smaller_so_the_loop_terminates() {
485        let t = types();
486        for run in 0..50 {
487            let v = arbitrary(&Ty::list(Ty::con("Event")), &t, &mut Rng::seeded("s", run)).unwrap();
488            for c in shrink(&v) {
489                assert!(size(&c) < size(&v), "{c:?} is not smaller than {v:?}");
490            }
491        }
492        assert!(shrink(&Value::Int(0)).is_empty());
493        assert!(shrink(&Value::List(Arc::new(Vec::new()))).is_empty());
494    }
495
496    #[test]
497    fn a_type_parameter_reaches_the_field_it_stands_for() {
498        let t = types();
499        let v = canonical(&Ty::app(Ty::OPTION, vec![Ty::int()]), &t).unwrap();
500        // `Option`'s first variant is `Some(value: a)`, and `a` is `Int` here.
501        assert_eq!(v.variant(), Some("Some"));
502        assert_eq!(v.field("value"), Some(&Value::Int(0)));
503    }
504}