beck_core/
core.rs

1//! `Core` — the load-bearing IR, and the evaluator that runs it.
2//!
3//! [`docs/04-compiler-architecture.md`](../../../../../docs/04-compiler-architecture.md) §4.2:
4//! "Typed ANF/SSA hybrid; explicit closures, explicit effect operations, explicit tier annotation
5//! per node; `Query` sub-language kept *symbolic*. Typechecked semantics, placement, splitting,
6//! optimisation. The load-bearing IR."
7//!
8//! Two things stay symbolic here, exactly as §4.2 demands, and for the same stated reason —
9//! lowering them early would foreclose Phase 3:
10//!
11//! * **Signal and stream operations** are [`Prim`]s ([`Prim::Fold`], [`Prim::Durable`],
12//!   [`Prim::SignalMap`], …), not loops. A fold that has already become a loop cannot be compiled
13//!   to an incremental dataflow plan.
14//! * **UI trees** are `Html` *values* built by [`Prim::HtmlEl`] and friends, not DOM mutation
15//!   calls, so the same value can be server-side rendered, diffed, or (Phase 3) compiled for the
16//!   client.
17//!
18//! # On the backend
19//!
20//! The roadmap names Cranelift as Phase 1's server backend. What is here instead is a **`Core`
21//! evaluator**: the "engine-in-Rust with the language as its configuration" route that
22//! [`docs/00-original-idea.md`](../../../../../docs/00-original-idea.md) names as one of the three
23//! that work for a GC'd functional language on a Rust host (Materialize's shape). It is the
24//! deliberately-bad-but-complete option, and it keeps the `Core → Target` seam narrow, which §5.2
25//! says is what lets a backend slot in later. The Phase 1 report says plainly that native codegen
26//! is not done.
27
28use std::fmt;
29use std::sync::Arc;
30
31use beck_diag::Span;
32
33use crate::html::Html;
34use crate::pmap::PMap;
35use crate::ty::{Effect, Tier, Ty};
36
37pub type VarId = u32;
38
39/// A primitive operation. Everything the standard library provides in Phase 1.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub enum Prim {
42    Add,
43    Sub,
44    Mul,
45    Div,
46    Rem,
47    Neg,
48    /// `abs`, `sqrt` and `Int` → `Float`, which are the three the numeric tower needs before any
49    /// of SICP §1.1.7 will run (`docs/32` §32.1). `Abs` is resolved from its operand the way the
50    /// arithmetic operators are; the other two are monomorphic.
51    Abs,
52    Sqrt,
53    Sin,
54    Cos,
55    Trunc,
56    ToFloat,
57    Eq,
58    Ne,
59    Lt,
60    Le,
61    Gt,
62    Ge,
63    And,
64    Or,
65    Not,
66    ToStr,
67    StrTrim,
68    StrToInt,
69    // ---- strings (docs/45 §? — Wave 2's string half). One primitive each, because a string is
70    // where a language's host has to be asked and there is nothing to express in Beck itself.
71    StrLen,
72    StrSlice,
73    StrSplit,
74    StrJoin,
75    StrContains,
76    StrStartsWith,
77    StrEndsWith,
78    StrUpper,
79    StrLower,
80    StrReplace,
81    StrIndexOf,
82    StrRepeat,
83    StrChars,
84    StrIsEmpty,
85    ListLen,
86    ListIsEmpty,
87    // ---- collections
88    ListGet,
89    ListSlice,
90    ListReverse,
91    ListTake,
92    ListDrop,
93    ListContains,
94    ListIndexOf,
95    ListFold,
96    ListAll,
97    ListAny,
98    ListFlatMap,
99    ListZip,
100    ListAppend,
101    MapKeys,
102    MapMerge,
103    // ---- JSON, and the standard library's first fallible function.
104    //
105    // `json_parse` raises rather than returning a `Result`, which is the shape
106    // [`docs/45`](../../../../../docs/45-error-rows-report.md) settled and the reason §8.5.3's trap 2
107    // said the library had to wait for it: a caller that wants a `Result` writes `try:`, and a
108    // caller inside something already fallible writes nothing at all.
109    JsonParse,
110    JsonRender,
111    // ---- time. `now()` gives milliseconds; these two are the civil calendar over them.
112    TimeFormat,
113    TimeParse,
114    // ---- digests, encodings and identifiers (`crate::digest`). A hash is a table and base64 is a
115    // grammar, so both are the host's half of `lib/README.md`'s division; a digest is also a *pure*
116    // function, which is what separates this from the other things a crypto library offers.
117    Digest,
118    /// A message authentication code: the one primitive whose input is a `secret[Str]` and whose
119    /// output is a `Str`. Charged `cap.sign` rather than left free, so a view cannot mint one —
120    /// `docs/adr/0014` is the record of the decision and §3.5 is what it is measured against.
121    DigestKeyed,
122    /// Constant-time equality, for the caller comparing a digest against one that arrived.
123    DigestEq,
124    HexEncode,
125    HexDecode,
126    Base64Encode,
127    Base64Decode,
128    /// Validates and *normalises*: two spellings of one identifier must not be two map keys.
129    UuidParse,
130    UuidVersion,
131    // ---- the outbound call. The *second* primitive whose row is a function of its argument
132    // (`Raise` is the first): the host it is given is the `net.out(host)` atom it performs, which
133    // is why that argument has to be written at the call site rather than computed.
134    HttpFetch,
135    MapList,
136    FilterList,
137    ConcatLists,
138    SortBy,
139    MapGet,
140    MapInsert,
141    MapRemove,
142    MapValues,
143    MapContains,
144    MapLen,
145    OptionIsSome,
146    OptionUnwrapOr,
147    HtmlEl,
148    HtmlText,
149    HtmlAttr,
150    HtmlOn,
151    HtmlKey,
152    /// Mints a fresh id. Nondeterministic, so §3.7 forbids it inside a fold; the client mints
153    /// entity ids instead, which is "the small tell that browsers here are replicas, not
154    /// terminals".
155    NewUuid,
156    /// Reads the wall clock. The other half of §3.7's rule: "time is data on the envelope".
157    Now,
158    /// Reads a secret from the process environment, yielding a `secret[Str]` (§3.5).
159    SecretEnv,
160    /// `raise e` — fail with a value.
161    ///
162    /// The atom it performs is `raises(T)`, which depends on the *type* of its argument, so the
163    /// checker attaches it where that type is known rather than [`Prim::effects`] declaring it.
164    /// This is the first primitive whose row is not a constant, and it is why that table's doc
165    /// says "the atoms this primitive performs *itself*".
166    Raise,
167    /// `try: block` — run a thunk, and turn a raise of the named type into an `Err`.
168    ///
169    /// Two arguments: the thunk, and the name of the error type this handler catches. The name is
170    /// what stops a handler from catching a failure it cannot type — a caller's function may raise
171    /// something this `try` never heard of, and that has to keep travelling.
172    Try,
173    /// `parallel: block` — run the scope's children, then its tail with their results bound.
174    ///
175    /// The arguments are the children's thunks followed by the continuation, so a child cannot
176    /// outlive the scope: there is no handle, and the only thing that can read a child's result is
177    /// the one lambda the scope built. That is [`docs/38`](../../../../../docs/38-literature-survey.md)
178    /// §38.4's "spawn/await as effect operations, the scope as their handler" with the handler as
179    /// the *only* form — the operations are not separately reachable.
180    ///
181    /// The children are independent by construction (none of them can name another) and no child
182    /// may perform an effect another child could observe, so the scope's answer does not depend on
183    /// the order they ran in. A backend may therefore run them together; running them in the order
184    /// they are written is a correct implementation of that, and is what the tree-walker does.
185    Parallel,
186    /// Wraps a value as `internal[T]`: storable, never Sendable.
187    InternalOf,
188    /// Unwraps one. Performs `cap.internal`, so only the authority chokepoint can do it.
189    Reveal,
190    // ---- the symbolic signal vocabulary (§3.7) ----
191    MergeClients,
192    /// `presence()` — who is connected now, as a signal that is not a function of the log.
193    ///
194    /// D6's last row. It performs `cap.presence` rather than an atom of its own, which is F16
195    /// ([`docs/14`](../../../../../docs/14-review-findings.md)) taken literally: "presence signals
196    /// leak who-is-online; gate behind a capability like any other view". The capability is also
197    /// what places it — no tier below the server discharges a `cap.*` — so a fold cannot read the
198    /// roster and a view reaches it across a declared edge.
199    Presence,
200    StreamFilterMap,
201    Fold,
202    Durable,
203    SignalMap,
204    SignalMap2,
205    /// §3.8's per-session view: `todos.map(filter_by(session.user))`. First-class because "the
206    /// fanout cost becomes a first-class engineering concern".
207    PerSession,
208    /// The authority chokepoint: the sole consumer of ingress, holding the accumulator so that
209    /// first-writer-wins and ownership can be decided (§3.7, F2).
210    Decide,
211}
212
213impl Prim {
214    pub fn name(self) -> &'static str {
215        use Prim::*;
216        match self {
217            Add => "+",
218            Sub => "-",
219            Mul => "*",
220            Div => "/",
221            Rem => "%",
222            Neg => "negate",
223            Raise => "raise",
224            Try => "try",
225            Parallel => "parallel",
226            Abs => "abs",
227            Sqrt => "sqrt",
228            Sin => "sin",
229            Cos => "cos",
230            Trunc => "trunc",
231            ToFloat => "float",
232            Eq => "==",
233            Ne => "!=",
234            Lt => "<",
235            Le => "<=",
236            Gt => ">",
237            Ge => ">=",
238            And => "and",
239            Or => "or",
240            Not => "not",
241            ToStr => "str",
242            StrTrim => "str_trim",
243            StrToInt => "str_to_int",
244            StrLen => "str_len",
245            StrSlice => "str_slice",
246            StrSplit => "str_split",
247            StrJoin => "str_join",
248            StrContains => "str_contains",
249            StrStartsWith => "str_starts_with",
250            StrEndsWith => "str_ends_with",
251            StrUpper => "str_upper",
252            StrLower => "str_lower",
253            StrReplace => "str_replace",
254            StrIndexOf => "str_index_of",
255            StrRepeat => "str_repeat",
256            StrChars => "str_chars",
257            ListGet => "list_get",
258            ListSlice => "list_slice",
259            ListReverse => "list_reverse",
260            ListTake => "list_take",
261            ListDrop => "list_drop",
262            ListContains => "list_contains",
263            ListIndexOf => "list_index_of",
264            ListFold => "list_fold",
265            ListAll => "list_all",
266            ListAny => "list_any",
267            ListFlatMap => "list_flat_map",
268            ListZip => "list_zip_with",
269            ListAppend => "list_append",
270            MapKeys => "map_keys",
271            MapMerge => "map_merge",
272            JsonParse => "json_parse",
273            JsonRender => "json_render",
274            TimeFormat => "time_format",
275            TimeParse => "time_parse",
276            Digest => "digest",
277            DigestKeyed => "digest_keyed",
278            DigestEq => "digest_eq",
279            HexEncode => "hex_encode",
280            HexDecode => "hex_decode",
281            Base64Encode => "base64_encode",
282            Base64Decode => "base64_decode",
283            UuidParse => "uuid_parse",
284            UuidVersion => "uuid_version",
285            HttpFetch => "http_fetch",
286            StrIsEmpty => "str_is_empty",
287            ListLen => "list_len",
288            ListIsEmpty => "list_is_empty",
289            MapList => "map_list",
290            FilterList => "filter_list",
291            ConcatLists => "concat_lists",
292            SortBy => "sort_by",
293            MapGet => "map_get",
294            MapInsert => "map_insert",
295            MapRemove => "map_remove",
296            MapValues => "map_values",
297            MapContains => "map_contains",
298            MapLen => "map_len",
299            OptionIsSome => "is_some",
300            OptionUnwrapOr => "unwrap_or",
301            HtmlEl => "html_el",
302            HtmlText => "html_text",
303            HtmlAttr => "html_attr",
304            HtmlOn => "html_on",
305            HtmlKey => "html_key",
306            NewUuid => "uuid",
307            Now => "now",
308            SecretEnv => "secret_env",
309            InternalOf => "internal_of",
310            Reveal => "reveal",
311            MergeClients => "merge_clients",
312            Presence => "presence",
313            StreamFilterMap => "filter_map",
314            Fold => "fold",
315            Durable => "durable",
316            SignalMap => "signal_map",
317            SignalMap2 => "map2",
318            PerSession => "per_session",
319            Decide => "decide",
320        }
321    }
322
323    /// The atoms this primitive performs *itself*.
324    ///
325    /// The polymorphic half of a primitive's row — `map_list`'s `e`, which is whatever its function
326    /// argument does — lives in the scheme in [`crate::prelude`], because a row variable is not a
327    /// constant. A test holds the two in agreement.
328    pub fn effects(self) -> Vec<Effect> {
329        match self {
330            // "Every connected client's send!s, interleaved. Arbitrary order — this is the
331            // nondeterminism; there is exactly one of these."
332            Prim::MergeClients => vec![Effect::Ingress],
333            // Who is connected is not a function of the log, so this is the second source of
334            // nondeterminism in a Beck program — and the only one a *view* may read. The atom is a
335            // capability rather than a new label: F16 asks for exactly that, and `cap.*` is what
336            // keeps it off the tiers that would make it unreplayable (§3.3).
337            Prim::Presence => vec![Effect::Cap(std::sync::Arc::from("presence"))],
338            Prim::Durable => vec![Effect::Durable],
339            Prim::NewUuid | Prim::Now => vec![Effect::Nondet],
340            // The scope performs `spawn` itself; what its children perform is charged by the
341            // checker, from each child's own row, because a thunk's effects belong to the thunk's
342            // type and this is the form that calls them.
343            Prim::Parallel => vec![Effect::Spawn],
344            Prim::SecretEnv => vec![Effect::Env],
345            // Wrapping is free; *reading* is the privileged half, and the capability is what stops
346            // a view unwrapping one to render it.
347            Prim::Reveal => vec![Effect::Cap(std::sync::Arc::from("internal"))],
348            // The standard library's fallible pair. Unlike `Prim::Raise`, whose atom depends on
349            // its argument's type, these two raise a type the prelude declares — so the row *is* a
350            // constant and belongs here, where a test holds it against the scheme.
351            Prim::JsonParse => vec![Effect::Raises(std::sync::Arc::from("JsonError"))],
352            Prim::TimeParse => vec![Effect::Raises(std::sync::Arc::from("TimeError"))],
353            Prim::HexDecode | Prim::Base64Decode => {
354                vec![Effect::Raises(std::sync::Arc::from("EncodingError"))]
355            }
356            Prim::UuidParse | Prim::UuidVersion => {
357                vec![Effect::Raises(std::sync::Arc::from("UuidError"))]
358            }
359            // The declassifier. `digest` and the encodings are pure; this one reads a
360            // `secret[Str]`, so it is held where `reveal` is held — behind a capability no client
361            // tier discharges, which is what stops a view minting a token (`docs/adr/0014`).
362            Prim::DigestKeyed => vec![Effect::Cap(std::sync::Arc::from("sign"))],
363            // Half of `http_fetch`'s row is a constant and half is its first argument. The
364            // constant half is here; the `net.out(host)` half is added by [`Core::effects`],
365            // which can see the argument, and by the checker, which is where it is charged.
366            Prim::HttpFetch => vec![Effect::Raises(std::sync::Arc::from("HttpError"))],
367            _ => Vec::new(),
368        }
369    }
370}
371
372#[derive(Clone, Debug, PartialEq)]
373pub enum Const {
374    Unit,
375    Bool(bool),
376    Int(i64),
377    Float(f64),
378    Str(Arc<str>),
379}
380
381/// One arm of a `match`. Patterns are shallow — a constructor and its named field binders — which
382/// is what §3.1's exhaustiveness check needs and no more.
383#[derive(Clone, Debug)]
384pub struct Arm {
385    pub pattern: Pattern,
386    /// `case Circle(r) if r > 0:` — a condition on the arm, in the scope of what the pattern bound.
387    ///
388    /// A guard that fails falls through to the next arm, which is what makes it a guard rather
389    /// than an `if` in the body.
390    pub guard: Option<Core>,
391    pub body: Core,
392    pub span: Span,
393}
394
395impl Arm {
396    /// Every expression this arm holds, in the order they run.
397    ///
398    /// One method rather than `&a.body` at each of fourteen call sites, for
399    /// [`Pattern::binders`]'s reason and with its history: a guard added as a field those sites
400    /// did not know about would be a `Core` that liveness never marks, that `frames` never counts
401    /// a slot for, and that the plan's free-variable analysis never sees — none of which is a
402    /// compile error, and all of which are wrong on a program that uses one.
403    pub fn exprs(&self) -> impl Iterator<Item = &Core> {
404        self.guard.iter().chain(std::iter::once(&self.body))
405    }
406
407    pub fn exprs_mut(&mut self) -> impl Iterator<Item = &mut Core> {
408        self.guard.iter_mut().chain(std::iter::once(&mut self.body))
409    }
410}
411
412#[derive(Clone, Debug)]
413pub enum Pattern {
414    Wildcard,
415    Bind(VarId),
416    Const(Const),
417    /// `Added(id, text)` — `variant` names the constructor, and each field carries the pattern
418    /// matched against it.
419    ///
420    /// A field's pattern is usually a [`Pattern::Bind`] or a [`Pattern::Wildcard`], which is what
421    /// `Added(id, text)` and `Added(_)` mean. It may be any pattern: `Some(Added(id, text))` is a
422    /// `Ctor` whose one field is a `Ctor`.
423    Ctor {
424        variant: Arc<str>,
425        binds: Vec<(Arc<str>, Pattern)>,
426    },
427    /// `whole @ Circle(r)` — a name for the value, and a pattern that takes it apart.
428    ///
429    /// The binder is irrefutable, so whether this matches is entirely `inner`'s question.
430    At {
431        var: VarId,
432        inner: Box<Pattern>,
433    },
434    /// `Circle(r) | Square(r)` — one of several, and every alternative binds the same names.
435    ///
436    /// The checker unifies the alternatives' binders onto one set of variables, so the body reads
437    /// `r` without knowing which alternative matched. That is the rule that makes an or-pattern a
438    /// pattern rather than two arms sharing a body.
439    Or(Vec<Pattern>),
440    /// `[]`, `[x]`, `[a, b]`, `[first, *rest]` — a list, taken apart.
441    ///
442    /// `items` is one pattern per fixed element and `rest` is the optional tail binder. A pattern
443    /// with no `rest` matches a list of exactly `items.len()` elements; one with a `rest` matches
444    /// any list at least that long.
445    ///
446    /// The tail is a binder rather than a pattern, and deliberately: `[a, *[b, c]]` is `[a, b, c]`
447    /// written twice over, so what it would add is a second spelling rather than a shape.
448    List {
449        items: Vec<Pattern>,
450        rest: Option<Option<VarId>>,
451    },
452}
453
454impl Pattern {
455    /// Every variable this pattern binds, at any depth.
456    ///
457    /// One method rather than a `match` at each of the three call sites, because those three were
458    /// `Bind`/`Ctor`/`_ => {}` — and a new pattern kind falling into the `_` would have been a
459    /// silent miscount in the splitter's variable supply and a false *free* variable in the plan's
460    /// analysis. Neither would have failed a test until a program used one (`docs/33` §33.5).
461    pub fn binders(&self) -> Vec<VarId> {
462        let mut out = Vec::new();
463        self.collect_binders(&mut out);
464        out
465    }
466
467    fn collect_binders(&self, out: &mut Vec<VarId>) {
468        match self {
469            Pattern::Wildcard | Pattern::Const(_) => {}
470            Pattern::Bind(v) => out.push(*v),
471            Pattern::At { var, inner } => {
472                out.push(*var);
473                inner.collect_binders(out);
474            }
475            Pattern::Ctor { binds, .. } => {
476                for (_, p) in binds {
477                    p.collect_binders(out);
478                }
479            }
480            Pattern::List { items, rest } => {
481                for p in items {
482                    p.collect_binders(out);
483                }
484                out.extend(rest.iter().filter_map(|b| *b));
485            }
486            // Every alternative binds the same variables, so one would do; all of them, deduped,
487            // is what stays right if the checker's unification is ever wrong about that.
488            Pattern::Or(alts) => {
489                for p in alts {
490                    p.collect_binders(out);
491                }
492                out.sort_unstable();
493                out.dedup();
494            }
495        }
496    }
497
498    /// Whether this pattern matches every value of its type, so that no arm after it can run.
499    ///
500    /// Only a binder and a wildcard do — and a list pattern of nothing but a tail, `[*rest]`,
501    /// which is the one refutable-looking shape that refuses nothing.
502    pub fn irrefutable(&self) -> bool {
503        match self {
504            Pattern::Wildcard | Pattern::Bind(_) => true,
505            Pattern::List { items, rest } => items.is_empty() && rest.is_some(),
506            Pattern::Or(alts) => alts.iter().any(Pattern::irrefutable),
507            Pattern::At { inner, .. } => inner.irrefutable(),
508            Pattern::Const(_) | Pattern::Ctor { .. } => false,
509        }
510    }
511}
512
513#[derive(Clone, Debug)]
514pub enum CoreKind {
515    Const(Const),
516    Var(VarId),
517    /// A reference to a top-level definition.
518    Global(Arc<str>),
519    Lam {
520        /// Shared for the same reason `body` is: evaluating a `lam` hands the list to a closure,
521        /// and a refcount bump is cheaper than copying it once per call.
522        params: Arc<[VarId]>,
523        /// Shared, not owned: a closure is built every time a `lam` node is *evaluated*, and a
524        /// `Box` meant deep-copying the whole function body each time. `docs/73` §73.1 measured
525        /// 20,000 calls to a function whose executed path never changed costing 42 ms, 227 ms and
526        /// 606 ms as the *unexecuted* part of its body grew.
527        body: Arc<Core>,
528    },
529    App {
530        func: Box<Core>,
531        args: Vec<Core>,
532    },
533    Prim {
534        op: Prim,
535        args: Vec<Core>,
536    },
537    Let {
538        var: VarId,
539        value: Box<Core>,
540        body: Box<Core>,
541    },
542    If {
543        cond: Box<Core>,
544        then: Box<Core>,
545        alt: Box<Core>,
546    },
547    Match {
548        scrutinee: Box<Core>,
549        arms: Vec<Arm>,
550    },
551    /// Construct a union variant or a model record.
552    Make {
553        ty: Arc<str>,
554        variant: Option<Arc<str>>,
555        fields: Vec<(Arc<str>, Core)>,
556    },
557    Field {
558        base: Box<Core>,
559        name: Arc<str>,
560    },
561    /// `t.with(done=not t.done)` — a functional record update. The sketch's
562    /// `(set t :done (not t.done))`, and the reason `Todo` never needs a mutable binding.
563    With {
564        base: Box<Core>,
565        fields: Vec<(Arc<str>, Core)>,
566    },
567    ListLit(Vec<Core>),
568    MapLit(Vec<(Core, Core)>),
569}
570
571#[derive(Clone, Debug)]
572pub struct Core {
573    pub kind: CoreKind,
574    pub ty: Ty,
575    /// Which tier this node runs on. §4.2: "explicit tier annotation per node".
576    pub tier: Tier,
577    pub span: Span,
578    /// Set on a [`CoreKind::Var`] whose value this expression is the **last** reader of, so a
579    /// backend may move the binding rather than copy it. [`crate::liveness`] is what sets it and
580    /// what the guarantee means; `false` is always safe.
581    pub last_use: bool,
582    /// Set on a [`CoreKind::Make`]: which written field belongs at each position of the record it
583    /// builds, packed four bits per field. [`crate::fields`] is what sets it and what the packing
584    /// means; [`crate::fields::UNORDERED`] is always safe and means "sort at run time".
585    ///
586    /// It costs nothing: a `u32` here fits in the padding `last_use` already leaves, so `Core` is
587    /// 160 bytes either way.
588    pub order: u32,
589    /// Set on a [`CoreKind::Lam`]: how many bindings its body makes, so a call can reserve room
590    /// for them in one frame instead of allocating a scope per `let`. [`crate::frames`] is what
591    /// sets it and what the count means; `0` is always safe and means "chain a scope, as before".
592    pub locals: u32,
593}
594
595/// The string this expression *is*, when it is written as one.
596///
597/// Deliberately not an evaluator and deliberately not a constant folder: the one caller is the
598/// host of an outbound call, and "the host is written at the call site" is the rule that makes an
599/// egress policy derivable. A `let` that happens to bind a literal is not written at the call
600/// site, and reading through one would make the rule depend on how much folding the compiler
601/// currently does.
602pub fn literal_str(c: &Core) -> Option<Arc<str>> {
603    match &c.kind {
604        CoreKind::Const(Const::Str(s)) => Some(s.clone()),
605        _ => None,
606    }
607}
608
609impl Core {
610    pub fn new(kind: CoreKind, ty: Ty, span: Span) -> Core {
611        Core {
612            kind,
613            ty,
614            tier: Tier::Any,
615            span,
616            last_use: false,
617            order: crate::fields::UNORDERED,
618            locals: 0,
619        }
620    }
621
622    /// Every effect this expression can perform, by walking what it calls.
623    ///
624    /// Phase 1 used this as *the* effect analysis. Phase 2 does not: rows are inferred during
625    /// checking, where a call's latent row is known and a mere *reference* to a function performs
626    /// nothing. What survives is this syntactic over-approximation, used in one place where that is
627    /// the right answer — asking what a fold's function body could reach, including through a
628    /// function value it was handed.
629    pub fn effects(&self, globals: &dyn Fn(&str) -> Vec<Effect>, out: &mut Vec<Effect>) {
630        match &self.kind {
631            CoreKind::Prim { op, args } => {
632                for e in op.effects() {
633                    if !out.contains(&e) {
634                        out.push(e);
635                    }
636                }
637                // `http_fetch`'s host is its first argument and a literal, so this walk can read
638                // it. It matters here rather than only in the checker because this is the oracle
639                // `testing::performs_itself` asks — a definition that makes the call is the one a
640                // `stub net.out(host)` replaces, and a definition that merely calls it is not.
641                if let (Prim::HttpFetch, Some(host)) = (op, args.first().and_then(literal_str)) {
642                    let atom = Effect::NetOut(host);
643                    if !out.contains(&atom) {
644                        out.push(atom);
645                    }
646                }
647                for a in args {
648                    a.effects(globals, out);
649                }
650            }
651            CoreKind::Global(name) => {
652                for e in globals(name) {
653                    if !out.contains(&e) {
654                        out.push(e);
655                    }
656                }
657            }
658            CoreKind::Const(_) | CoreKind::Var(_) => {}
659            CoreKind::Lam { body, .. } => body.effects(globals, out),
660            CoreKind::App { func, args } => {
661                func.effects(globals, out);
662                for a in args {
663                    a.effects(globals, out);
664                }
665            }
666            CoreKind::Let { value, body, .. } => {
667                value.effects(globals, out);
668                body.effects(globals, out);
669            }
670            CoreKind::If { cond, then, alt } => {
671                cond.effects(globals, out);
672                then.effects(globals, out);
673                alt.effects(globals, out);
674            }
675            CoreKind::Match { scrutinee, arms } => {
676                scrutinee.effects(globals, out);
677                for e in arms.iter().flat_map(|a| a.exprs()) {
678                    e.effects(globals, out);
679                }
680            }
681            CoreKind::Make { fields, .. } => {
682                for (_, f) in fields {
683                    f.effects(globals, out);
684                }
685            }
686            CoreKind::Field { base, .. } => base.effects(globals, out),
687            CoreKind::With { base, fields } => {
688                base.effects(globals, out);
689                for (_, f) in fields {
690                    f.effects(globals, out);
691                }
692            }
693            CoreKind::ListLit(xs) => {
694                for x in xs {
695                    x.effects(globals, out);
696                }
697            }
698            CoreKind::MapLit(kvs) => {
699                for (k, v) in kvs {
700                    k.effects(globals, out);
701                    v.effects(globals, out);
702                }
703            }
704        }
705    }
706
707    /// Set the tier on this node and everything under it.
708    pub fn place(&mut self, tier: Tier) {
709        self.tier = tier;
710        match &mut self.kind {
711            CoreKind::Lam { body, .. } => Arc::make_mut(body).place(tier),
712            CoreKind::App { func, args } => {
713                func.place(tier);
714                for a in args {
715                    a.place(tier);
716                }
717            }
718            CoreKind::Prim { args, .. } => {
719                for a in args {
720                    a.place(tier);
721                }
722            }
723            CoreKind::Let { value, body, .. } => {
724                value.place(tier);
725                body.place(tier);
726            }
727            CoreKind::If { cond, then, alt } => {
728                cond.place(tier);
729                then.place(tier);
730                alt.place(tier);
731            }
732            CoreKind::Match { scrutinee, arms } => {
733                scrutinee.place(tier);
734                for e in arms.iter_mut().flat_map(|a| a.exprs_mut()) {
735                    e.place(tier);
736                }
737            }
738            CoreKind::Make { fields, .. } => {
739                for (_, f) in fields {
740                    f.place(tier);
741                }
742            }
743            CoreKind::Field { base, .. } => base.place(tier),
744            CoreKind::With { base, fields } => {
745                base.place(tier);
746                for (_, f) in fields {
747                    f.place(tier);
748                }
749            }
750            CoreKind::ListLit(xs) => {
751                for x in xs {
752                    x.place(tier);
753                }
754            }
755            CoreKind::MapLit(kvs) => {
756                for (k, v) in kvs {
757                    k.place(tier);
758                    v.place(tier);
759                }
760            }
761            CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => {}
762        }
763    }
764}
765
766// ---------------------------------------------------------------------------------------------
767// Values
768// ---------------------------------------------------------------------------------------------
769
770/// A runtime value.
771///
772/// `Map` and a record's fields are both *ordered* on purpose and for the same reason Phase 0 chose
773/// `BTreeMap`: iteration order is part of the rendered view, and replay must reproduce the *patch
774/// stream* bit for bit, not merely the set of values.
775///
776/// `Map` is a [`PMap`], not an `Arc<BTreeMap>`, because it is the fold's accumulator: an update
777/// must not copy it. See [`crate::pmap`] for why that structure and not another.
778#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
779pub enum Value {
780    Unit,
781    Bool(bool),
782    Int(i64),
783    /// A real, stored as an **order-preserving** key rather than as `f64::to_bits`, so that the
784    /// derived `Ord` is the numeric one.
785    ///
786    /// A total order is not optional here — a map key and a component of the state digest need one
787    /// — and `to_bits` supplies one that disagrees with arithmetic: `-1.0` has a larger bit pattern
788    /// than `1.0`, so `<` answered backwards for every negative number and `sort_by` sorted the
789    /// negatives in reverse. [`Value::float`] applies the standard monotone transform instead
790    /// (flip the sign bit for a positive, invert every bit for a negative), which makes the two
791    /// orders the same order. `docs/32` §32.2.
792    Float(u64),
793    /// Text, with the two facts a character-indexed language needs about it: how many characters
794    /// there are, and whether a character index is a byte index. [`Text`] is why.
795    Str(Arc<Text>),
796    List(Arc<Vec<Value>>),
797    Map(PMap<Value, Value>),
798    /// A model instance or a union variant — see [`Record`].
799    ///
800    /// Behind one pointer rather than inline, and that is a size decision rather than a style one:
801    /// the three fields inline made **every** `Value` 48 bytes, so a list of a million integers
802    /// carried 32 bytes of nothing per element and a call frame paid for the widest variant it did
803    /// not hold. One `Arc` makes a `Value` 16.
804    Data(Arc<Record>),
805    Html(Arc<Html>),
806    /// An attribute waiting to be attached to an element.
807    Attr(Arc<AttrValue>),
808    Closure(Arc<Closure>),
809}
810
811/// A model instance or a union variant. `variant` is `None` for a plain record.
812///
813/// Split out of [`Value::Data`] so that a `Value` is a discriminant and a pointer. Records are the
814/// widest thing the language has and the rarest thing in a hot loop, which is exactly the shape
815/// that should be behind an indirection.
816#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
817pub struct Record {
818    pub ty: Arc<str>,
819    pub variant: Option<Arc<str>>,
820    pub fields: Fields,
821}
822
823/// A record's fields, sorted by name.
824///
825/// This was a `BTreeMap`, and a record is the wrong size for one: three to eight entries, built
826/// once and read many times. A B-tree pays a node allocation and a pointer chase per level to buy
827/// an asymptotic advantage that never arrives at that size, and profiling `awfy/havlak.beck` put
828/// a fifth of the process inside its search, its insert and the `memcmp` underneath them.
829///
830/// Sorted by name and searched linearly: one allocation for the whole record, the names lie next
831/// to each other in cache, and `get` compares lengths before bytes because it wants equality
832/// rather than order. Iteration is in name order, so the value order, the state digest and the
833/// wire format ([`crate::repr`]) are all bit-for-bit what the `BTreeMap` gave — which is what
834/// makes this a representation change and not a semantic one.
835#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
836pub struct Fields(Vec<(Arc<str>, Value)>);
837
838impl Fields {
839    pub fn new() -> Fields {
840        Fields(Vec::new())
841    }
842
843    pub fn with_capacity(n: usize) -> Fields {
844        Fields(Vec::with_capacity(n))
845    }
846
847    pub fn get(&self, name: &str) -> Option<&Value> {
848        self.0
849            .iter()
850            .find(|(k, _)| same_name(k, name))
851            .map(|(_, v)| v)
852    }
853
854    /// Set `name`, keeping the order by name. Answers the value that was there.
855    ///
856    /// The search is by **equality** and not by order, which is the whole difference: `==` on two
857    /// `str`s compares their lengths first and reaches `memcmp` only for a pair that could match,
858    /// where a binary search has to order every probe it makes. A record has three to eight
859    /// fields, so a scan makes at most as many comparisons as a binary search and nearly all of
860    /// them are an integer test. Only a field that is genuinely new pays for the ordered insert,
861    /// and `with` — which is what calls this in a loop — never has one.
862    pub fn insert(&mut self, name: Arc<str>, value: Value) -> Option<Value> {
863        if let Some(slot) = self.0.iter_mut().find(|(k, _)| same_name(k, &name)) {
864            return Some(std::mem::replace(&mut slot.1, value));
865        }
866        let at = self
867            .0
868            .partition_point(|(k, _)| cmp_name(k, &name) == std::cmp::Ordering::Less);
869        self.0.insert(at, (name, value));
870        None
871    }
872
873    /// Build from fields in **any** order, sorting once.
874    ///
875    /// This is how a record literal is built, and it is a separate entry point from `insert` in a
876    /// loop because the two cost differently: `sort_unstable_by` on a handful of elements is an
877    /// insertion sort, which makes `n - 1` comparisons and moves nothing when the fields already
878    /// arrive in order — as a record literal's usually do.
879    pub fn from_pairs(mut pairs: Vec<(Arc<str>, Value)>) -> Fields {
880        pairs.sort_unstable_by(|(a, _), (b, _)| cmp_name(a, b));
881        pairs.dedup_by(|(a, _), (b, _)| same_name(a, b));
882        Fields(pairs)
883    }
884
885    /// Build from fields the caller has **already** put in order.
886    ///
887    /// The caller is [`crate::fields`], which decided the order once at compile time — a record
888    /// literal's field names are written in the source, so sorting them once per record built is
889    /// work with a known answer. Nothing else should use this: the order is the `Map` iteration,
890    /// the state digest and the patch stream, so getting it wrong is a wire-format bug rather than
891    /// a slow lookup.
892    pub fn from_sorted(pairs: Vec<(Arc<str>, Value)>) -> Fields {
893        debug_assert!(
894            pairs
895                .windows(2)
896                .all(|w| cmp_name(&w[0].0, &w[1].0) == std::cmp::Ordering::Less),
897            "from_sorted was given fields that are not sorted and distinct"
898        );
899        Fields(pairs)
900    }
901
902    pub fn len(&self) -> usize {
903        self.0.len()
904    }
905
906    pub fn is_empty(&self) -> bool {
907        self.0.is_empty()
908    }
909
910    pub fn iter(&self) -> std::slice::Iter<'_, (Arc<str>, Value)> {
911        self.0.iter()
912    }
913
914    pub fn values(&self) -> impl Iterator<Item = &Value> {
915        self.0.iter().map(|(_, v)| v)
916    }
917}
918
919impl FromIterator<(Arc<str>, Value)> for Fields {
920    fn from_iter<I: IntoIterator<Item = (Arc<str>, Value)>>(it: I) -> Fields {
921        Fields::from_pairs(it.into_iter().collect())
922    }
923}
924
925impl<'a> IntoIterator for &'a Fields {
926    type Item = &'a (Arc<str>, Value);
927    type IntoIter = std::slice::Iter<'a, (Arc<str>, Value)>;
928    fn into_iter(self) -> Self::IntoIter {
929        self.0.iter()
930    }
931}
932
933/// A string that knows its own length in **characters**, and whether it is ASCII.
934///
935/// Beck indexes text by character everywhere or nowhere (`docs/50` §50.5), and a `String` counts
936/// bytes — so `str_len` used to be `chars().count()` and `str_slice` used to `skip()` its way to
937/// the start. Both are `O(n)` in the *string* rather than in the answer, which makes the ordinary
938/// way to walk one — `while i < str_len(s)` reading `str_slice(s, i, 1)` — quadratic. Measured at
939/// ×2.7 per doubling in [`70`](../../../../../docs/70-last-use-moves-report.md) §70.6.
940///
941/// Both facts are computed once, when the string is built, which is work the construction was
942/// already doing: it had to copy the bytes, and `is_ascii` is a scan of the same bytes that answers
943/// `chars` for free when it is true. Everything downstream is then `O(1)` or `O(answer)`.
944///
945/// The `String` rather than a `Box<str>` is the other half: it has spare capacity, so `a + b` can
946/// push into `a` when the last-use analysis proves nobody else holds it ([`crate::liveness`]).
947#[derive(Clone, Debug)]
948pub struct Text {
949    bytes: String,
950    /// Characters, not bytes. `str_len`'s answer.
951    chars: usize,
952    /// Every character is one byte, so character index == byte index and a slice is a byte range.
953    ascii: bool,
954    /// For text that is *not* ASCII: the byte offset of every 32nd character.
955    ///
956    /// Chunked rather than one entry per character, because the point is to stop paying `O(n)` per
957    /// slice and a jump to the nearest 32 does that for a thirty-second of the memory — `n / 8`
958    /// bytes, and only for text that needs it, since an ASCII character index *is* a byte index.
959    ///
960    /// Built eagerly, in the pass that counts the characters, rather than cached on first use. A
961    /// lazy one would be interior mutability inside a `Value`, and a `Value` is a `Map` key: the
962    /// cache would be invisible to `Ord` and `Hash` and therefore harmless, but "harmless interior
963    /// mutability in a key" is a sentence every reader and `clippy::mutable_key_type` would have to
964    /// re-check. One pass and an eighth of the bytes is the cheaper answer.
965    index: Box<[u32]>,
966}
967
968/// How many characters one entry of [`Text`]'s index skips.
969const INDEX_STRIDE: usize = 32;
970
971impl Text {
972    pub fn new(bytes: String) -> Text {
973        let ascii = bytes.is_ascii();
974        // ASCII answers both questions from the scan `is_ascii` already did, and needs no index at
975        // all. Anything else pays one more pass — once, here — rather than paying it on every
976        // `str_len` and every `str_slice`.
977        if ascii {
978            let chars = bytes.len();
979            return Text {
980                bytes,
981                chars,
982                ascii,
983                index: Box::new([]),
984            };
985        }
986        let mut chars = 0usize;
987        let mut index = Vec::with_capacity(bytes.len() / INDEX_STRIDE + 1);
988        for (at, _) in bytes.char_indices() {
989            if chars.is_multiple_of(INDEX_STRIDE) {
990                index.push(at as u32);
991            }
992            chars += 1;
993        }
994        Text {
995            bytes,
996            chars,
997            ascii,
998            index: index.into_boxed_slice(),
999        }
1000    }
1001
1002    /// The byte offset of character `i`, in constant time for ASCII text and in at most
1003    /// one index stride for anything else. Past the end it answers the end, which is what a
1004    /// clamping slice wants.
1005    pub fn byte_offset(&self, i: usize) -> usize {
1006        if self.ascii {
1007            return i.min(self.bytes.len());
1008        }
1009        if i >= self.chars {
1010            return self.bytes.len();
1011        }
1012        let chunk = i / INDEX_STRIDE;
1013        let from = self.index.get(chunk).copied().unwrap_or(0) as usize;
1014        match self.bytes[from..]
1015            .char_indices()
1016            .nth(i - chunk * INDEX_STRIDE)
1017        {
1018            Some((at, _)) => from + at,
1019            None => self.bytes.len(),
1020        }
1021    }
1022
1023    /// The length in characters, in constant time.
1024    pub fn chars_len(&self) -> usize {
1025        self.chars
1026    }
1027
1028    pub fn is_ascii_text(&self) -> bool {
1029        self.ascii
1030    }
1031
1032    pub fn as_str(&self) -> &str {
1033        &self.bytes
1034    }
1035
1036    /// Append, consuming: the caller has established sole ownership, so this is a `push_str` and
1037    /// not a copy of both sides.
1038    pub fn appended(mut self, other: &str) -> Text {
1039        let before = self.chars;
1040        let start = self.bytes.len();
1041        let other_ascii = other.is_ascii();
1042        self.bytes.push_str(other);
1043
1044        // Everything here is `O(other)`, never `O(self)`, which is the property the whole change
1045        // exists for: appending in a loop has to stay linear in the total.
1046        if self.ascii && other_ascii {
1047            self.chars = self.bytes.len();
1048            return self;
1049        }
1050        if self.ascii {
1051            // The left half was ASCII, so its character numbers *are* its byte offsets and its
1052            // share of the index can be written down rather than walked for.
1053            let mut index = Vec::with_capacity(self.bytes.len() / INDEX_STRIDE + 1);
1054            let mut at = 0;
1055            while at < before {
1056                index.push(at as u32);
1057                at += INDEX_STRIDE;
1058            }
1059            self.index = index.into_boxed_slice();
1060            self.ascii = false;
1061        }
1062        let mut index = std::mem::take(&mut self.index).into_vec();
1063        let mut chars = before;
1064        for (at, _) in self.bytes[start..].char_indices() {
1065            if chars.is_multiple_of(INDEX_STRIDE) {
1066                index.push((start + at) as u32);
1067            }
1068            chars += 1;
1069        }
1070        self.chars = chars;
1071        self.index = index.into_boxed_slice();
1072        self
1073    }
1074}
1075
1076impl std::ops::Deref for Text {
1077    type Target = str;
1078    fn deref(&self) -> &str {
1079        &self.bytes
1080    }
1081}
1082
1083impl From<&str> for Text {
1084    fn from(s: &str) -> Text {
1085        Text::new(s.to_string())
1086    }
1087}
1088
1089/// Text compares, orders and hashes **as its characters**, so that adding the two cached facts
1090/// cannot change what a program means: a `Map` keyed by strings keeps its order, and so does the
1091/// state digest that a replay has to reproduce.
1092impl PartialEq for Text {
1093    fn eq(&self, other: &Self) -> bool {
1094        self.bytes == other.bytes
1095    }
1096}
1097impl Eq for Text {}
1098impl PartialOrd for Text {
1099    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1100        Some(self.cmp(other))
1101    }
1102}
1103impl Ord for Text {
1104    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1105        self.bytes.cmp(&other.bytes)
1106    }
1107}
1108impl std::hash::Hash for Text {
1109    fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1110        self.bytes.hash(h)
1111    }
1112}
1113
1114#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1115pub enum AttrValue {
1116    Plain(Arc<str>, Arc<str>),
1117    On(Arc<str>, Value),
1118    Key(Arc<str>),
1119}
1120
1121#[derive(Debug)]
1122pub struct Closure {
1123    pub params: Arc<[VarId]>,
1124    /// The same `Arc` the [`CoreKind::Lam`] node holds, so building a closure is a refcount bump
1125    /// rather than a copy of the code.
1126    pub body: Arc<Core>,
1127    /// Behind an `Arc` so that *calling* the closure clones a pointer rather than the environment.
1128    pub env: Arc<Env>,
1129    /// How many bindings the body makes, copied off the [`CoreKind::Lam`] node so that a call can
1130    /// size one frame for the parameters and all of them. [`crate::frames`] is what counts it.
1131    pub locals: u32,
1132}
1133
1134impl PartialEq for Closure {
1135    /// Closures compare by identity of their code position. Two closures are never equal unless
1136    /// they came from the same lambda with the same captured frame, which is all any program
1137    /// should rely on.
1138    fn eq(&self, other: &Self) -> bool {
1139        self.params == other.params
1140            && self.body.span == other.body.span
1141            && self.env.frame == other.env.frame
1142    }
1143}
1144impl Eq for Closure {}
1145impl PartialOrd for Closure {
1146    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1147        Some(self.cmp(other))
1148    }
1149}
1150impl Ord for Closure {
1151    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1152        self.params
1153            .cmp(&other.params)
1154            .then_with(|| self.body.span.start.cmp(&other.body.span.start))
1155    }
1156}
1157
1158/// A lexical environment: a persistent chain of frames, so a closure can capture cheaply.
1159#[derive(Clone, Debug, Default, PartialEq, Eq)]
1160pub struct Env {
1161    /// `Arc<[T]>` rather than `Arc<Vec<T>>`: the second is two allocations and two hops to reach a
1162    /// binding.
1163    ///
1164    /// A call sizes this for the parameters **and** for every binding the body will make, so a
1165    /// `let` writes into a slot that is already there rather than allocating a scope of its own.
1166    /// The unwritten tail is filled with [`TOMBSTONE`], which no variable is named.
1167    frame: Arc<[(VarId, Value)]>,
1168    /// How much of `frame` holds a binding. Everything from here up is reserved and empty.
1169    used: u32,
1170    parent: Option<Arc<Env>>,
1171}
1172
1173impl Env {
1174    pub fn new() -> Env {
1175        Env::default()
1176    }
1177
1178    pub fn extend(&self, bindings: Vec<(VarId, Value)>) -> Env {
1179        Env {
1180            used: bindings.len() as u32,
1181            frame: bindings.into(),
1182            parent: Some(Arc::new(self.clone())),
1183        }
1184    }
1185
1186    /// The frame a call runs in: the parameters, then `locals` reserved slots for the bindings the
1187    /// body is going to make.
1188    ///
1189    /// One allocation. `Map<Range, _>` has a length the compiler can trust, so collecting it into
1190    /// an `Arc<[_]>` sizes the allocation once — which is why the parameters and the reserved tail
1191    /// are produced by one iterator rather than a vector that is then converted.
1192    pub fn call_frame(
1193        parent: &Arc<Env>,
1194        params: &[VarId],
1195        args: impl Iterator<Item = Value>,
1196        locals: u32,
1197    ) -> Env {
1198        let n = params.len();
1199        let mut given = args;
1200        let frame = (0..n + locals as usize)
1201            .map(|i| match given.next() {
1202                Some(v) => (params[i], v),
1203                None => (TOMBSTONE, Value::Unit),
1204            })
1205            .collect();
1206        Env {
1207            frame,
1208            used: n as u32,
1209            parent: Some(Arc::clone(parent)),
1210        }
1211    }
1212
1213    /// Bind `bindings` into this frame's reserved tail, if there is room for all of them and
1214    /// nobody else is holding the frame.
1215    ///
1216    /// Answers whether it did. `false` means the caller must fall back to [`Env::extend`] and
1217    /// chain a scope — which happens when a closure has captured this environment (its clone holds
1218    /// the frame, so `Arc::get_mut` refuses), when the reservation was too small, or when the
1219    /// program was built by something that never ran the reservation pass at all.
1220    ///
1221    /// The safety argument is the refusal: a closure that captured this environment can see the
1222    /// slots this would write, and `Arc::get_mut` is what proves that has not happened. Every
1223    /// binding gets a slot of its own, so nothing a closure captured is ever overwritten.
1224    pub fn put(&mut self, bindings: &mut Vec<(VarId, Value)>) -> bool {
1225        let at = self.used as usize;
1226        let n = bindings.len();
1227        if at + n > self.frame.len() {
1228            return false;
1229        }
1230        let Some(frame) = Arc::get_mut(&mut self.frame) else {
1231            return false;
1232        };
1233        for (i, b) in bindings.drain(..).enumerate() {
1234            frame[at + i] = b;
1235        }
1236        self.used = (at + n) as u32;
1237        true
1238    }
1239
1240    /// [`Env::put`] for a single binding, which is what a `let` is — and a `let` is much the most
1241    /// common of the two, so it does not build a vector to hand over. Answers the value back when
1242    /// there is no room for it.
1243    pub fn put_one(&mut self, var: VarId, value: Value) -> Result<(), Value> {
1244        let at = self.used as usize;
1245        if at >= self.frame.len() {
1246            return Err(value);
1247        }
1248        match Arc::get_mut(&mut self.frame) {
1249            Some(frame) => {
1250                frame[at] = (var, value);
1251                self.used += 1;
1252                Ok(())
1253            }
1254            None => Err(value),
1255        }
1256    }
1257
1258    /// The part of the frame that holds bindings, without the reserved tail.
1259    #[inline]
1260    fn bound(&self) -> &[(VarId, Value)] {
1261        &self.frame[..self.used as usize]
1262    }
1263
1264    /// Extend a parent that is **already** behind an `Arc` — which a closure's captured environment
1265    /// is, so that a call clones a pointer instead of boxing a copy of the environment.
1266    ///
1267    /// This is the per-call path. `extend` above is the per-`let` path, where the parent is owned by
1268    /// the evaluator's loop and has to be boxed; there are far fewer of those.
1269    pub fn extend_shared(parent: &Arc<Env>, frame: Arc<[(VarId, Value)]>) -> Env {
1270        Env {
1271            used: frame.len() as u32,
1272            frame,
1273            parent: Some(Arc::clone(parent)),
1274        }
1275    }
1276
1277    /// An environment with nothing in it, behind an `Arc`, shared by every top-level definition.
1278    pub fn empty_shared() -> Arc<Env> {
1279        Arc::new(Env::new())
1280    }
1281
1282    pub fn get(&self, v: VarId) -> Option<&Value> {
1283        let mut env = self;
1284        loop {
1285            if let Some((_, value)) = env.bound().iter().rev().find(|(id, _)| *id == v) {
1286                return Some(value);
1287            }
1288            match &env.parent {
1289                Some(p) => env = p,
1290                None => return None,
1291            }
1292        }
1293    }
1294
1295    /// Read `v`, and **move** it out of the frame when three things hold: the caller says no later
1296    /// evaluation reads it, this environment is the only holder of the frame it lives in, and the
1297    /// value is one whose copy costs something.
1298    ///
1299    /// The third condition is not an optimisation of an optimisation — it is what keeps the other
1300    /// two from costing more than they save. Moving is strictly more work than cloning at the point
1301    /// of the read: a clone of an `Int` is a copy of eight bytes and a clone of a container is one
1302    /// atomic increment, where a move has to find the slot, prove the frame is unshared and empty
1303    /// it. It pays only when somebody downstream can then *use* the sole ownership — which today is
1304    /// `list_append` pushing in place and `with` rebuilding a record's fields — and measuring it
1305    /// without this condition showed every benchmark in the tree 6–13% slower, because the reads
1306    /// that dominate a real program are of `Int`s and nothing was gained by moving one
1307    /// ([`69`](../../../../../docs/69-standard-library-imports-report.md) §69.7).
1308    ///
1309    /// The caller must have established that no later evaluation reads `v` — [`crate::liveness`]
1310    /// is what establishes it, and `last_use` is the flag. What this adds is the second half of the
1311    /// safety argument: a frame is emptied only when nothing else holds it, so an environment
1312    /// captured by a closure or shared with an inner scope is read from rather than emptied, and a
1313    /// caller that is wrong about liveness gets an unbound-variable error rather than somebody
1314    /// else's missing binding.
1315    pub fn read(&mut self, v: VarId, may_move: bool) -> Option<Value> {
1316        // The overwhelmingly common read is not a last use, and it needs none of the machinery
1317        // below: no scope on the way to the binding has to be proved unshared, because nothing is
1318        // going to be taken out of one. That proof costs **two atomic loads per scope level**
1319        // (`strong_count` and `weak_count`) plus an `Arc::get_mut`, and it was being paid on every
1320        // variable a program reads. `get` is a plain walk.
1321        if !may_move {
1322            return self.get(v).cloned();
1323        }
1324        let mut env = self;
1325        loop {
1326            if let Some(i) = env.bound().iter().rposition(|(id, _)| *id == v) {
1327                if may_move && worth_moving(&env.frame[i].1) {
1328                    if let Some(frame) = Arc::get_mut(&mut env.frame) {
1329                        // Tombstoned rather than removed: `Vec::remove` shifts every binding above
1330                        // it, and this runs on the hottest path there is. The slot keeps its place
1331                        // under a name no variable has, so a later read of `v` misses it and says
1332                        // so instead of finding a neighbour.
1333                        frame[i].0 = TOMBSTONE;
1334                        return Some(std::mem::replace(&mut frame[i].1, Value::Unit));
1335                    }
1336                }
1337                return Some(env.frame[i].1.clone());
1338            }
1339            let shared_parent = env
1340                .parent
1341                .as_ref()
1342                .is_some_and(|p| Arc::strong_count(p) > 1 || Arc::weak_count(p) > 0);
1343            if shared_parent {
1344                return env.parent.as_ref().and_then(|p| p.get(v)).cloned();
1345            }
1346            match env.parent.as_mut() {
1347                Some(p) => match Arc::get_mut(p) {
1348                    Some(p) => env = p,
1349                    None => return None,
1350                },
1351                None => return None,
1352            }
1353        }
1354    }
1355}
1356
1357/// Every subexpression of this one, to be rewritten in place.
1358///
1359/// The walk two passes over the finished program share — [`crate::frames`] and [`crate::fields`].
1360/// A lambda's body is behind an `Arc` because a closure shares it rather than copying it
1361/// (`docs/73`), so reaching into one is a `make_mut`: it runs once, on a program nothing else
1362/// holds yet, so nothing is actually cloned.
1363pub(crate) fn children_mut(c: &mut Core) -> Vec<&mut Core> {
1364    match &mut c.kind {
1365        CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => Vec::new(),
1366        CoreKind::Lam { body, .. } => vec![Arc::make_mut(body)],
1367        CoreKind::App { func, args } => std::iter::once(&mut **func).chain(args).collect(),
1368        CoreKind::Let { value, body, .. } => vec![&mut **value, &mut **body],
1369        CoreKind::If { cond, then, alt } => vec![&mut **cond, &mut **then, &mut **alt],
1370        CoreKind::Match { scrutinee, arms } => std::iter::once(&mut **scrutinee)
1371            .chain(arms.iter_mut().flat_map(|a| a.exprs_mut()))
1372            .collect(),
1373        CoreKind::Prim { args, .. } => args.iter_mut().collect(),
1374        CoreKind::Make { fields, .. } => fields.iter_mut().map(|(_, f)| f).collect(),
1375        CoreKind::Field { base, .. } => vec![&mut **base],
1376        CoreKind::With { base, fields } => std::iter::once(&mut **base)
1377            .chain(fields.iter_mut().map(|(_, f)| f))
1378            .collect(),
1379        CoreKind::ListLit(items) => items.iter_mut().collect(),
1380        CoreKind::MapLit(kvs) => kvs.iter_mut().flat_map(|(k, v)| [k, v]).collect(),
1381    }
1382}
1383
1384/// Are these the same field name?
1385///
1386/// Length, then first byte, then the rest. `str`'s own `==` checks the length and hands the bytes
1387/// to `memcmp`, and a `memcmp` call is dear next to what it decides here: field names are short,
1388/// there are three to eight of them in a record, and two that share a length almost never share a
1389/// first letter. Profiling `awfy/richards.beck` put 6% of the process inside `memcmp`, nearly all
1390/// of it deciding between `kind` and `link`.
1391#[inline]
1392fn same_name(a: &str, b: &str) -> bool {
1393    let (x, y) = (a.as_bytes(), b.as_bytes());
1394    x.len() == y.len() && (x.is_empty() || x[0] == y[0]) && x == y
1395}
1396
1397/// Order two field names, deciding on the first byte where it can.
1398///
1399/// `<[u8]>::cmp` calls `memcmp` over the common prefix before it looks at the lengths, so it pays
1400/// a call to distinguish `id` from `kind`. Sorting a record's fields is the other half of what
1401/// `same_name` documents.
1402#[inline]
1403pub(crate) fn cmp_name(a: &str, b: &str) -> std::cmp::Ordering {
1404    let (x, y) = (a.as_bytes(), b.as_bytes());
1405    match (x.first(), y.first()) {
1406        (Some(p), Some(q)) if p != q => p.cmp(q),
1407        _ => x.cmp(y),
1408    }
1409}
1410
1411/// The name a moved-out binding takes, which no variable has: `VarId`s are handed out from zero by
1412/// the checker, and a program with four billion of them has lost to `MAX_NESTING` long before.
1413const TOMBSTONE: VarId = VarId::MAX;
1414
1415/// Whether moving this value out of a frame can save anything downstream.
1416///
1417/// A `List` can be pushed into by `list_append`, a `Str` by `+`, and a record's fields can be
1418/// rebuilt in place by `with` — each only when nobody else holds them. Everything else is either a copy of a few bytes
1419/// or an atomic increment, and moving it costs more than it saves — `Env::read` is the measurement.
1420fn worth_moving(v: &Value) -> bool {
1421    matches!(v, Value::List(_) | Value::Str(_) | Value::Data(_))
1422}
1423
1424/// The monotone `f64` → `u64` transform: for a non-negative float flip the sign bit, for a
1425/// negative one invert every bit. `a < b` as reals iff `order_key(a) < order_key(b)` as integers,
1426/// with `-inf` at the bottom and NaN above `+inf`.
1427const SIGN: u64 = 1 << 63;
1428
1429fn order_key(f: f64) -> u64 {
1430    let bits = f.to_bits();
1431    if bits & SIGN != 0 {
1432        !bits
1433    } else {
1434        bits ^ SIGN
1435    }
1436}
1437
1438fn from_order_key(key: u64) -> f64 {
1439    f64::from_bits(if key & SIGN != 0 { key ^ SIGN } else { !key })
1440}
1441
1442impl Value {
1443    /// Make a real, canonicalising the two IEEE values that would otherwise break `Eq`.
1444    ///
1445    /// `-0.0` becomes `0.0` and every NaN becomes one NaN, because [`Value`] is `Eq` and `Ord` and
1446    /// a fold's accumulator is compared, hashed and used as a map key. IEEE 754 says `NaN != NaN`
1447    /// and `-0.0 == 0.0`; both are irreconcilable with a total order, and the total order is the
1448    /// one §3.7 needs. So Beck's `==` on reals is *structural*, and `docs/32` §32.2 says so where
1449    /// somebody porting numeric code will read it.
1450    pub fn float(f: f64) -> Value {
1451        let f = if f.is_nan() {
1452            f64::NAN
1453        } else if f == 0.0 {
1454            0.0
1455        } else {
1456            f
1457        };
1458        Value::Float(order_key(f))
1459    }
1460
1461    pub fn as_f64(&self) -> Option<f64> {
1462        match self {
1463            Value::Float(key) => Some(from_order_key(*key)),
1464            _ => None,
1465        }
1466    }
1467
1468    pub fn str_(s: impl AsRef<str>) -> Value {
1469        Value::Str(Arc::new(Text::from(s.as_ref())))
1470    }
1471
1472    /// The same from a `String` that is already owned, which is most of the string primitives:
1473    /// they build one and would otherwise copy it again on the way in.
1474    pub fn text(s: String) -> Value {
1475        Value::Str(Arc::new(Text::new(s)))
1476    }
1477
1478    pub fn as_str(&self) -> Option<&str> {
1479        match self {
1480            Value::Str(s) => Some(s),
1481            _ => None,
1482        }
1483    }
1484
1485    pub fn as_int(&self) -> Option<i64> {
1486        match self {
1487            Value::Int(i) => Some(*i),
1488            _ => None,
1489        }
1490    }
1491
1492    pub fn as_bool(&self) -> Option<bool> {
1493        match self {
1494            Value::Bool(b) => Some(*b),
1495            _ => None,
1496        }
1497    }
1498
1499    pub fn as_list(&self) -> Option<&Vec<Value>> {
1500        match self {
1501            Value::List(xs) => Some(xs),
1502            _ => None,
1503        }
1504    }
1505
1506    pub fn as_map(&self) -> Option<&PMap<Value, Value>> {
1507        match self {
1508            Value::Map(m) => Some(m),
1509            _ => None,
1510        }
1511    }
1512
1513    pub fn as_html(&self) -> Option<&Html> {
1514        match self {
1515            Value::Html(h) => Some(h),
1516            _ => None,
1517        }
1518    }
1519
1520    /// Build a record or a union variant. The one constructor, so that the `Arc` and the map are
1521    /// allocated in one place rather than at every call site.
1522    pub fn data(ty: impl Into<Arc<str>>, variant: Option<Arc<str>>, fields: Fields) -> Value {
1523        Value::Data(Arc::new(Record {
1524            ty: ty.into(),
1525            variant,
1526            fields,
1527        }))
1528    }
1529
1530    /// The same from a list of pairs, which is how most call sites have them.
1531    pub fn record<const N: usize>(
1532        ty: impl Into<Arc<str>>,
1533        variant: Option<&str>,
1534        fields: [(&str, Value); N],
1535    ) -> Value {
1536        Value::data(
1537            ty,
1538            variant.map(Arc::from),
1539            fields.into_iter().map(|(k, v)| (Arc::from(k), v)).collect(),
1540        )
1541    }
1542
1543    pub fn field(&self, name: &str) -> Option<&Value> {
1544        match self {
1545            Value::Data(d) => d.fields.get(name),
1546            _ => None,
1547        }
1548    }
1549
1550    pub fn variant(&self) -> Option<&str> {
1551        match self {
1552            Value::Data(d) => d.variant.as_deref(),
1553            _ => None,
1554        }
1555    }
1556
1557    pub fn some(v: Value) -> Value {
1558        Value::record(Ty::OPTION, Some("Some"), [("value", v)])
1559    }
1560
1561    pub fn none() -> Value {
1562        Value::record(Ty::OPTION, Some("None"), [])
1563    }
1564
1565    pub fn ok(v: Value) -> Value {
1566        Value::record(Ty::RESULT, Some("Ok"), [("value", v)])
1567    }
1568
1569    pub fn err(v: Value) -> Value {
1570        Value::record(Ty::RESULT, Some("Err"), [("error", v)])
1571    }
1572
1573    /// How `str(x)` renders a value, and how a value becomes a `Map` key's printed form.
1574    pub fn display(&self) -> String {
1575        match self {
1576            Value::Unit => "unit".into(),
1577            Value::Bool(b) => b.to_string(),
1578            Value::Int(i) => i.to_string(),
1579            Value::Float(_) => format!("{}", self.as_f64().unwrap_or(0.0)),
1580            Value::Str(s) => s.to_string(),
1581            Value::List(xs) => {
1582                let parts: Vec<String> = xs.iter().map(Value::display).collect();
1583                format!("[{}]", parts.join(", "))
1584            }
1585            Value::Map(m) => {
1586                let parts: Vec<String> = m
1587                    .iter()
1588                    .map(|(k, v)| format!("{}: {}", k.display(), v.display()))
1589                    .collect();
1590                format!("{{{}}}", parts.join(", "))
1591            }
1592            Value::Data(d) => {
1593                // A newtype wrapping one field prints as that field — `Id(uuid)` reads as the uuid,
1594                // which is what a key attribute and a rendered list want.
1595                if d.variant.is_none() && d.fields.len() == 1 {
1596                    if let Some(v) = d.fields.values().next() {
1597                        return v.display();
1598                    }
1599                }
1600                let name = d.variant.as_deref().unwrap_or(&d.ty);
1601                if d.fields.is_empty() {
1602                    return name.to_string();
1603                }
1604                let parts: Vec<String> = d
1605                    .fields
1606                    .iter()
1607                    .map(|(k, v)| format!("{k}: {}", v.display()))
1608                    .collect();
1609                format!("{name}{{{}}}", parts.join(", "))
1610            }
1611            Value::Html(h) => h.render(),
1612            Value::Attr(_) => "<attr>".into(),
1613            Value::Closure(_) => "<fn>".into(),
1614        }
1615    }
1616
1617    /// The wire form, used for command payloads carried by handlers and for the log.
1618    pub fn to_json(&self) -> serde_json::Value {
1619        use serde_json::{Map as JMap, Value as J};
1620        match self {
1621            Value::Unit => J::Null,
1622            Value::Bool(b) => J::Bool(*b),
1623            Value::Int(i) => J::Number((*i).into()),
1624            Value::Float(_) => serde_json::Number::from_f64(self.as_f64().unwrap_or(0.0))
1625                .map(J::Number)
1626                .unwrap_or(J::Null),
1627            Value::Str(s) => J::String(s.to_string()),
1628            Value::List(xs) => J::Array(xs.iter().map(Value::to_json).collect()),
1629            Value::Map(m) => {
1630                let mut obj = JMap::new();
1631                for (k, v) in m.iter() {
1632                    obj.insert(k.display(), v.to_json());
1633                }
1634                J::Object(obj)
1635            }
1636            Value::Data(d) => {
1637                if d.variant.is_none() && d.fields.len() == 1 {
1638                    if let Some(v) = d.fields.values().next() {
1639                        return v.to_json();
1640                    }
1641                }
1642                let mut obj = JMap::new();
1643                if let Some(v) = &d.variant {
1644                    obj.insert("c".into(), J::String(v.to_string()));
1645                }
1646                for (k, v) in d.fields.iter() {
1647                    obj.insert(k.to_string(), v.to_json());
1648                }
1649                J::Object(obj)
1650            }
1651            Value::Html(h) => h.to_wire(),
1652            Value::Attr(_) | Value::Closure(_) => J::Null,
1653        }
1654    }
1655}
1656
1657impl fmt::Display for Value {
1658    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1659        f.write_str(&self.display())
1660    }
1661}
1662
1663/// A value that cannot be written to a log.
1664///
1665/// [`Value`] has four consumers with different requirements — the evaluator, the log, the wire, and
1666/// the digest — and three of its variants exist only for the first. Encoding one of those is not a
1667/// value to be lowered; it is a program that should not have compiled.
1668#[derive(Clone, Debug, PartialEq, Eq)]
1669pub struct NotStorable {
1670    /// Which variant, by name.
1671    pub kind: &'static str,
1672}
1673
1674impl fmt::Display for NotStorable {
1675    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1676        write!(
1677            f,
1678            "a {} cannot be written to the log: it is {}, not data",
1679            self.kind,
1680            match self.kind {
1681                "closure" => "code",
1682                "attribute" => "part of a view",
1683                _ => "a view",
1684            }
1685        )
1686    }
1687}
1688
1689impl std::error::Error for NotStorable {}
1690
1691/// A lossless encoding of a [`Value`], for the log and for snapshots.
1692///
1693/// [`Value::to_json`] is the *wire* form: it drops the type name of a record and unwraps a
1694/// newtype, because that is what a browser wants. The log needs the opposite — a record that can
1695/// be read back as exactly the value that was written, because replay compares digests. Hence two
1696/// encodings, and a test that says why.
1697///
1698/// # Why this returns a `Result`
1699///
1700/// It used to encode `Html`, `Attr` and `Closure` as `unit` on the grounds that "neither can appear
1701/// in a log". That grounds was an assumption, not a check: nothing stops a program declaring
1702/// `model State: cached: Html`, and the encoding would then write `unit` into the *durable* path,
1703/// silently, and replay would rebuild a different state. A system whose correctness argument is
1704/// "replay is exact" cannot have a lossy branch in the function that makes the log.
1705///
1706/// Until placement can prove such a type never reaches `durable` (Phase 2's effect rows), refusing
1707/// at the boundary is the honest position: the append fails, the process aborts by the same rule as
1708/// any other failed append (§18.5 item 6), and nothing unreadable is ever committed.
1709pub fn value_to_repr(v: &Value) -> Result<serde_json::Value, NotStorable> {
1710    use serde_json::{json, Map as JMap, Value as J};
1711    Ok(match v {
1712        Value::Unit => json!({"$": "unit"}),
1713        Value::Bool(b) => json!({"$": "bool", "v": b}),
1714        Value::Int(i) => json!({"$": "int", "v": i}),
1715        Value::Float(bits) => json!({"$": "float", "v": bits.to_string()}),
1716        Value::Str(s) => json!({"$": "str", "v": s.as_str()}),
1717        Value::List(xs) => {
1718            let items: Result<Vec<_>, _> = xs.iter().map(value_to_repr).collect();
1719            json!({"$": "list", "v": items?})
1720        }
1721        Value::Map(m) => {
1722            let mut pairs = Vec::with_capacity(m.len());
1723            for (k, val) in m.iter() {
1724                pairs.push(json!([value_to_repr(k)?, value_to_repr(val)?]));
1725            }
1726            json!({"$": "map", "v": pairs})
1727        }
1728        Value::Data(d) => {
1729            let mut f = JMap::new();
1730            for (k, val) in d.fields.iter() {
1731                f.insert(k.to_string(), value_to_repr(val)?);
1732            }
1733            json!({
1734                "$": "data",
1735                "t": d.ty.as_ref(),
1736                "c": d.variant.as_deref(),
1737                "f": J::Object(f)
1738            })
1739        }
1740        Value::Html(_) => return Err(NotStorable { kind: "view" }),
1741        Value::Attr(_) => return Err(NotStorable { kind: "attribute" }),
1742        Value::Closure(_) => return Err(NotStorable { kind: "closure" }),
1743    })
1744}
1745
1746pub fn value_from_repr(j: &serde_json::Value) -> Option<Value> {
1747    let tag = j.get("$")?.as_str()?;
1748    Some(match tag {
1749        "unit" => Value::Unit,
1750        "bool" => Value::Bool(j.get("v")?.as_bool()?),
1751        "int" => Value::Int(j.get("v")?.as_i64()?),
1752        "float" => Value::Float(j.get("v")?.as_str()?.parse().ok()?),
1753        "str" => Value::str_(j.get("v")?.as_str()?),
1754        "list" => Value::List(Arc::new(
1755            j.get("v")?
1756                .as_array()?
1757                .iter()
1758                .map(value_from_repr)
1759                .collect::<Option<Vec<_>>>()?,
1760        )),
1761        "map" => {
1762            let mut m = PMap::new();
1763            for pair in j.get("v")?.as_array()? {
1764                let pair = pair.as_array()?;
1765                m = m.insert(
1766                    value_from_repr(pair.first()?)?,
1767                    value_from_repr(pair.get(1)?)?,
1768                );
1769            }
1770            Value::Map(m)
1771        }
1772        "data" => {
1773            let mut fields = Fields::new();
1774            for (k, val) in j.get("f")?.as_object()? {
1775                fields.insert(Arc::from(k.as_str()), value_from_repr(val)?);
1776            }
1777            Value::data(
1778                Arc::from(j.get("t")?.as_str()?),
1779                j.get("c").and_then(|c| c.as_str()).map(Arc::from),
1780                fields,
1781            )
1782        }
1783        _ => return None,
1784    })
1785}
1786
1787/// Structural digest of a value — the replay-determinism oracle (§4.8).
1788///
1789/// A property of the *value*, not of whoever produced it: two backends that disagree are
1790/// detected by comparing digests, so the digest cannot live in either of them.
1791pub fn digest(v: &Value) -> [u8; 32] {
1792    let mut hasher = blake3::Hasher::new();
1793    hash_into(v, &mut hasher);
1794    *hasher.finalize().as_bytes()
1795}
1796
1797fn hash_into(v: &Value, h: &mut blake3::Hasher) {
1798    match v {
1799        Value::Unit => h.update(&[0]),
1800        Value::Bool(b) => h.update(&[1, *b as u8]),
1801        Value::Int(i) => {
1802            h.update(&[2]);
1803            h.update(&i.to_le_bytes())
1804        }
1805        Value::Float(bits) => {
1806            h.update(&[3]);
1807            h.update(&bits.to_le_bytes())
1808        }
1809        Value::Str(s) => {
1810            h.update(&[4]);
1811            h.update(&(s.len() as u64).to_le_bytes());
1812            h.update(s.as_bytes())
1813        }
1814        Value::List(xs) => {
1815            h.update(&[5]);
1816            h.update(&(xs.len() as u64).to_le_bytes());
1817            for x in xs.iter() {
1818                hash_into(x, h);
1819            }
1820            h
1821        }
1822        Value::Map(m) => {
1823            h.update(&[6]);
1824            h.update(&(m.len() as u64).to_le_bytes());
1825            for (k, val) in m.iter() {
1826                hash_into(k, h);
1827                hash_into(val, h);
1828            }
1829            h
1830        }
1831        Value::Data(d) => {
1832            h.update(&[7]);
1833            h.update(d.ty.as_bytes());
1834            h.update(d.variant.as_deref().unwrap_or("").as_bytes());
1835            h.update(&(d.fields.len() as u64).to_le_bytes());
1836            for (k, val) in d.fields.iter() {
1837                h.update(k.as_bytes());
1838                hash_into(val, h);
1839            }
1840            h
1841        }
1842        Value::Html(html) => {
1843            h.update(&[8]);
1844            h.update(html.render().as_bytes())
1845        }
1846        Value::Attr(_) => h.update(&[9]),
1847        Value::Closure(_) => h.update(&[10]),
1848    };
1849}
1850
1851#[cfg(test)]
1852mod tests {
1853    use super::*;
1854
1855    /// The property `Value::Float`'s representation exists for: the order the fold uses and the
1856    /// order arithmetic uses are one order.
1857    ///
1858    /// `f64::to_bits` does not have it — `(-1.0).to_bits()` is larger than `(1.0).to_bits()`
1859    /// because the sign bit is the top one — and that is the defect docs/32 §32.2 records. This is
1860    /// the test that would have caught it, checked across the sign, the zeroes and the infinities
1861    /// rather than on one example.
1862    #[test]
1863    fn reals_compare_as_reals_and_round_trip_through_their_key() {
1864        let ladder = [
1865            f64::NEG_INFINITY,
1866            -1e308,
1867            -1.5,
1868            -1.0,
1869            -f64::MIN_POSITIVE,
1870            0.0,
1871            f64::MIN_POSITIVE,
1872            1.0,
1873            1.5,
1874            1e308,
1875            f64::INFINITY,
1876        ];
1877        for w in ladder.windows(2) {
1878            let (a, b) = (Value::float(w[0]), Value::float(w[1]));
1879            assert!(a < b, "{} should order below {}", w[0], w[1]);
1880            assert_eq!(a.as_f64(), Some(w[0]), "and survive the round trip");
1881        }
1882
1883        // The two IEEE values that would otherwise break `Eq`, canonicalised.
1884        assert_eq!(
1885            Value::float(-0.0),
1886            Value::float(0.0),
1887            "`-0.0` and `0.0` are one value, because `Ord` cannot have two of them"
1888        );
1889        assert_eq!(
1890            Value::float(f64::NAN),
1891            Value::float(-f64::NAN),
1892            "and every NaN is one NaN — including a negative one — for the same reason"
1893        );
1894        assert!(
1895            Value::float(f64::NAN) > Value::float(f64::INFINITY),
1896            "NaN has to go somewhere, and above every number is somewhere"
1897        );
1898
1899        // The digest is a function of the value, and a different real is a different digest.
1900        assert_eq!(digest(&Value::float(1.5)), digest(&Value::float(1.5)));
1901        assert_ne!(digest(&Value::float(1.5)), digest(&Value::float(-1.5)));
1902    }
1903
1904    #[test]
1905    fn the_log_encoding_round_trips_exactly() {
1906        // The wire encoding deliberately loses information the browser does not need; the log
1907        // encoding cannot, because replay compares digests of what it reads back.
1908        let v = Value::data(
1909            Arc::from("State"),
1910            None,
1911            Fields::from_iter([(
1912                Arc::from("todos"),
1913                Value::Map(PMap::from_iter([(
1914                    Value::str_("k"),
1915                    Value::data(
1916                        Arc::from("Todo"),
1917                        None,
1918                        Fields::from_iter([
1919                            (Arc::from("done"), Value::Bool(true)),
1920                            (Arc::from("n"), Value::Int(-3)),
1921                        ]),
1922                    ),
1923                )])),
1924            )]),
1925        );
1926        assert_eq!(
1927            value_from_repr(&value_to_repr(&v).unwrap()),
1928            Some(v.clone())
1929        );
1930        let evt = Value::data(
1931            Arc::from("Event"),
1932            Some(Arc::from("Toggled")),
1933            Fields::from_iter([(Arc::from("id"), Value::str_("x"))]),
1934        );
1935        assert_eq!(value_from_repr(&value_to_repr(&evt).unwrap()), Some(evt));
1936    }
1937
1938    #[test]
1939    fn a_value_the_log_cannot_hold_is_refused_rather_than_flattened() {
1940        // This used to encode as `unit`, on the assumption that a view never reaches the log.
1941        // Nothing checked the assumption: `model State: cached: Html` compiles today, and the
1942        // durable path would have written `unit` and replayed a different state — silently, in the
1943        // one place this system's correctness argument does not permit silence.
1944        let view = Value::Html(Arc::new(crate::html::Html::text("hello")));
1945        let err = value_to_repr(&view).expect_err("a view is not data");
1946        assert!(
1947            err.to_string().contains("cannot be written to the log"),
1948            "{err}"
1949        );
1950
1951        // …and nesting does not launder it: a record holding one is refused too.
1952        let state = Value::data(
1953            Arc::from("State"),
1954            None,
1955            Fields::from_iter([(Arc::from("cached"), view.clone())]),
1956        );
1957        assert!(
1958            value_to_repr(&state).is_err(),
1959            "a record holding a view is not data"
1960        );
1961        assert!(
1962            value_to_repr(&Value::List(Arc::new(vec![view.clone()]))).is_err(),
1963            "a list holding a view is not data"
1964        );
1965        assert!(
1966            value_to_repr(&Value::Map(PMap::new().insert(Value::str_("k"), view))).is_err(),
1967            "a map holding a view is not data"
1968        );
1969    }
1970
1971    #[test]
1972    fn maps_order_by_key_so_rendering_is_deterministic() {
1973        let m = PMap::new()
1974            .insert(Value::str_("b"), Value::Int(2))
1975            .insert(Value::str_("a"), Value::Int(1));
1976        let v = Value::Map(m);
1977        assert_eq!(v.display(), "{a: 1, b: 2}");
1978    }
1979
1980    #[test]
1981    fn a_newtype_renders_as_its_payload() {
1982        let id = Value::data(
1983            Arc::from("Id"),
1984            None,
1985            Fields::from_iter([(Arc::from("value"), Value::str_("u-1"))]),
1986        );
1987        assert_eq!(id.display(), "u-1");
1988        assert_eq!(id.to_json(), serde_json::json!("u-1"));
1989    }
1990
1991    #[test]
1992    fn a_command_serialises_with_its_variant_tag() {
1993        let cmd = Value::data(
1994            Arc::from("Command"),
1995            Some(Arc::from("Toggle")),
1996            Fields::from_iter([(Arc::from("id"), Value::str_("x"))]),
1997        );
1998        assert_eq!(cmd.to_json(), serde_json::json!({"c": "Toggle", "id": "x"}));
1999    }
2000
2001    #[test]
2002    fn environments_shadow_innermost_first() {
2003        let base = Env::new().extend(vec![(1, Value::Int(1))]);
2004        let inner = base.extend(vec![(1, Value::Int(2))]);
2005        assert_eq!(inner.get(1), Some(&Value::Int(2)));
2006        assert_eq!(base.get(1), Some(&Value::Int(1)));
2007        assert_eq!(inner.get(9), None);
2008    }
2009
2010    #[test]
2011    fn only_the_impure_primitives_carry_atoms_of_their_own() {
2012        assert_eq!(Prim::MergeClients.effects(), vec![Effect::Ingress]);
2013        assert_eq!(Prim::Durable.effects(), vec![Effect::Durable]);
2014        assert_eq!(Prim::NewUuid.effects(), vec![Effect::Nondet]);
2015        assert_eq!(Prim::Now.effects(), vec![Effect::Nondet]);
2016        assert_eq!(Prim::SecretEnv.effects(), vec![Effect::Env]);
2017        assert!(Prim::Add.effects().is_empty());
2018        assert!(
2019            Prim::Fold.effects().is_empty(),
2020            "a fold is pure; `durable` is the effect"
2021        );
2022        assert!(
2023            Prim::MapList.effects().is_empty(),
2024            "`map_list` performs nothing of its own — it performs its argument's row, which is a \
2025             variable and lives in the scheme"
2026        );
2027    }
2028}