beck_core/check/
mod.rs

1//! Resolution and typechecking, elaborating straight into `Core`.
2//!
3//! Stages 4 and 5 of [`docs/04-compiler-architecture.md`](../../../../../docs/04-compiler-architecture.md)
4//! §4.1 — "modules, imports, name binding, hygiene scopes → resolved AST" and "HM + rows + effect
5//! rows + capabilities → typed AST" — run as one pass that emits stage 6's `Core` directly. §4.2
6//! allows exactly three IRs; a separate resolved-but-untyped tree would be a fourth.
7//!
8//! # Resolution is hygiene-aware
9//!
10//! A reference resolves to a binding when `binding.scopes ⊆ reference.scopes`, innermost first.
11//! That one rule is what makes the macro expander's work mean something: a binding a macro
12//! introduced carries a scope the call site does not have, so the call site cannot see it.
13//!
14//! # What Phase 1 checks, and what it does not
15//!
16//! Checked: HM inference with unification and let-polymorphism, ADTs (`union`), records (`model`),
17//! nominal newtypes, `match` exhaustiveness, mandatory annotations on top-level signatures, and
18//! the `Stream`/`Signal`/`fold`/`durable` types of §3.7.
19//!
20//! Phase 2 adds §3.2's **effect inference**. Every definition gets a row *variable* before any body
21//! is checked; checking a body accumulates the latent row of everything it applies; the variable is
22//! then bound to what accumulated. A mere *reference* to a function performs nothing — only
23//! applying one does — which is the difference between inference and Phase 1's syntactic collection,
24//! and the reason `fold(apply_event, …)` is not itself effectful.
25//!
26//! Mutual recursion needs no ordering: `f`'s row may be bound to a row mentioning `g`'s variable and
27//! vice versa, and [`Subst::resolve_row`] computes the least fixed point because a row is a union.
28//!
29//! Not checked, and named rather than implied: row polymorphism on records, trait constraints on
30//! type variables, and `var` mutability (a `var` binding is checked as an ordinary immutable one).
31
32use std::collections::{BTreeMap, BTreeSet};
33use std::sync::Arc;
34
35use beck_diag::depth::Nesting;
36use beck_diag::{Diagnostic, Diagnostics, Span};
37use beck_syntax::{sym, Lit, Node, ScopeSet, Symbol};
38
39use crate::core::{Arm, Const, Core, CoreKind, Pattern, Prim, VarId};
40use crate::iface::Interface;
41use crate::prelude;
42use crate::render;
43use crate::ty::{self, Effect, Mismatch, Row, RowVarId, Scheme, Subst, Tier, Ty, TyDecl, Variant};
44
45/// A checked module: everything the placement checker, the splitter and the runtime need.
46#[derive(Clone, Debug)]
47pub struct Program {
48    pub name: String,
49    pub types: BTreeMap<Arc<str>, TyDecl>,
50    /// The `trait` declarations this module owns, in declaration order.
51    pub traits: Vec<ty::TraitSig>,
52    /// The `impl` headers this module owns, in declaration order. Both cross a `.becki`: a call in
53    /// another module cannot resolve `item.pence()` without knowing the trait *and* that the impl
54    /// exists.
55    pub impls: Vec<ty::ImplSig>,
56    /// The types *this* module declares, in declaration order. An imported type is usable here and
57    /// published by the module that owns it, never by this one (§3.6).
58    pub own_types: Vec<Arc<str>>,
59    /// The interfaces this module was checked against.
60    pub imports: Vec<String>,
61    pub defs: BTreeMap<Arc<str>, Def>,
62    /// Source order, so diagnostics and `beck explain` are stable.
63    pub def_order: Vec<Arc<str>>,
64    pub signals: Vec<SignalDecl>,
65    pub tests: Vec<crate::testing::TestDef>,
66    /// The `##` doc comment attached to each declaration, keyed by the name it documents:
67    /// `todos` for a definition or signal, `Todo` for a type, `Todo.text` for a model field and
68    /// `Event.Toggled` for a union variant ([`beck_syntax::doc`]).
69    ///
70    /// A side table rather than a field on [`Def`], because documentation is not something the
71    /// checker, the solver or the runtime may read: nothing downstream of here may behave
72    /// differently because a definition is documented.
73    pub docs: BTreeMap<Arc<str>, Arc<str>>,
74    /// `identity = external(issuer="…")` — who authenticates this program's clients (D6).
75    ///
76    /// On the `Program` rather than in the runtime's configuration because it is a fact about the
77    /// program that §6.5's derivation needs: the issuer is a **peer**, and an egress rule is
78    /// derived from the peers a program names. `None` is the default provider, which believes
79    /// whatever a client says it is.
80    pub identity: Option<IdentityDecl>,
81}
82
83/// The host of an issuer URL, if it is one an egress rule could be written from.
84///
85/// `https` only, because the key set has no integrity protection but the transport
86/// ([`crate::net::is_nameable_host`] is the other half: a host with a port or a userinfo section
87/// is not a NetworkPolicy peer). Deliberately the same two refusals `beck_rt::oidc` makes at run
88/// time — stated here so the failure is a diagnostic rather than a deployment that will not
89/// authenticate anybody.
90fn issuer_host(url: &str) -> Option<String> {
91    let rest = url.strip_prefix("https://")?;
92    let authority = rest.split('/').next().unwrap_or(rest);
93    if authority.contains('@') {
94        return None;
95    }
96    let host = authority.rsplit_once(':').map_or(authority, |(h, _)| h);
97    crate::net::is_nameable_host(host).then(|| host.to_string())
98}
99
100/// Who authenticates a program's clients, as the program declared it.
101///
102/// D6's two forms, and the difference between them is a difference in *who provisions the
103/// provider* — which is why they are one declaration with two shapes rather than two features.
104/// [`IdentityDecl::External`] names somebody else's; [`IdentityDecl::Managed`] asks this
105/// deployment to stand one up, and the URL is then not the program's to write because it is a
106/// service the derivation has not named yet.
107#[derive(Clone, Debug, PartialEq, Eq)]
108pub enum IdentityDecl {
109    /// `identity = external(issuer="https://login.acme.com")`.
110    External {
111        /// The issuer URL, exactly as written. Both what is fetched and what every token's `iss` is
112        /// compared against.
113        issuer: Arc<str>,
114        /// The host of that URL — the peer §6.5's egress rule is written from, and the same shape
115        /// a `net.out(host)` atom carries.
116        host: Arc<str>,
117        span: Span,
118    },
119    /// `identity = managed()`.
120    ///
121    /// There is no URL here on purpose. The issuer is a `Service` §6.5 derives, so its name is a
122    /// function of the application's name and the platform's conventions — and a program that
123    /// wrote it down would be a program that has to be edited when either changes.
124    Managed { span: Span },
125}
126
127impl IdentityDecl {
128    /// Where it was written, so a second declaration in a second module has somewhere to point.
129    pub fn span(&self) -> Span {
130        match self {
131            IdentityDecl::External { span, .. } | IdentityDecl::Managed { span } => *span,
132        }
133    }
134}
135
136#[derive(Clone, Debug)]
137pub struct Def {
138    pub name: Arc<str>,
139    /// The names in `def map[T, U](…)`, in the order written.
140    ///
141    /// Carried on the `Def` rather than left in the scheme because `beck iface` publishes it: a
142    /// `.becki` line that dropped the `[T, U]` would read back as a signature mentioning two types
143    /// nobody declared (`docs/32` §32.8).
144    pub typarams: Vec<Arc<str>>,
145    pub params: Vec<(VarId, Arc<str>, Ty)>,
146    pub ret: Ty,
147    /// The whole definition as a lambda, so evaluating the name yields a callable value.
148    pub body: Core,
149    pub tier: Tier,
150    /// The inferred row's atoms, resolved and sorted — what placement and the infrastructure
151    /// derivation read.
152    pub effects: Vec<Effect>,
153    /// The inferred row itself, variables and all. This is the signature §3.6 publishes.
154    pub row: Row,
155    /// What the signature declared with `uses`, if anything.
156    pub declared_effects: Vec<Effect>,
157    /// The trait bounds on this definition's type parameters, in written order.
158    ///
159    /// Empty for almost everything. A bounded definition is **not published**: its dictionary
160    /// parameters carry names no source could write, and a trait does not cross a module boundary,
161    /// so `beck iface` drops it rather than publishing a signature nobody could call.
162    pub bounds: Vec<(Arc<str>, Vec<Arc<str>>)>,
163    /// True when the signature **stated** its row — so an empty one is a bound of "performs
164    /// nothing" rather than an absent declaration.
165    ///
166    /// A hand-written `def` with no `uses` has this false, because writing nothing means "infer
167    /// it". A trait method's row is stated by the trait, empty or not, so every impl method has it
168    /// true: otherwise `def show(self) -> Str` would let one implementation reach for a clock and
169    /// every caller of `show` would inherit it silently.
170    pub row_is_declared: bool,
171    /// True when the placement was written by hand rather than solved for (§3.4).
172    pub tier_is_annotated: bool,
173    /// A signature with nothing behind it: a line of a `.becki` interface, or a trait's method.
174    pub is_declaration: bool,
175    /// `@signal` — a declaration that publishes a signal rather than a function (§3.6).
176    pub declares_signal: bool,
177    pub span: Span,
178    pub tier_span: Span,
179}
180
181/// A top-level signal or stream declaration — the wiring of the program.
182#[derive(Clone, Debug)]
183pub struct SignalDecl {
184    pub name: Arc<str>,
185    pub ty: Ty,
186    pub expr: Core,
187    pub tier: Tier,
188    pub effects: Vec<Effect>,
189    pub row: Row,
190    pub tier_is_annotated: bool,
191    /// `@render(client|server)` — where this component's `view` runs, when it said so
192    /// ([`crate::render`]). Only a `Signal[Html]` can carry one, and nothing else reads it.
193    pub render: Option<(render::Mode, Span)>,
194    pub span: Span,
195    pub tier_span: Span,
196}
197
198/// Every decorator a top-level declaration may carry.
199///
200/// A struct rather than a tuple because it has grown twice: the tuple that returned a tier and a
201/// `@signal` flag had already stopped saying which was which.
202#[derive(Clone, Copy, Debug, Default)]
203struct Decorations {
204    tier: Option<(Tier, Span)>,
205    /// `@signal` — §3.6's marker for a published signal, which is a declaration of a *value*
206    /// rather than of a function.
207    declares_signal: bool,
208    render: Option<(render::Mode, Span)>,
209}
210
211/// The four types a test's clauses are checked against — see
212/// [`Checker::test_subjects`](Checker::test_subjects).
213#[derive(Clone, Debug, Default)]
214struct TestSubjects {
215    state: Option<Ty>,
216    event: Option<Ty>,
217    result: Option<Ty>,
218    command: Option<Ty>,
219}
220
221#[derive(Clone, Debug)]
222enum BindKind {
223    Local(VarId, Ty),
224    Global(Arc<str>),
225    Prim(Prim),
226    /// A trait method, resolved to an impl from the type of its receiver at each call site.
227    TraitMethod(Arc<str>),
228    /// A union variant; carries the union it belongs to.
229    Ctor(Arc<str>, Arc<str>),
230    /// A model, used as a constructor: `Todo(id=…, text=…)`.
231    Model(Arc<str>),
232}
233
234#[derive(Clone, Debug)]
235struct Binding {
236    name: Arc<str>,
237    scopes: ScopeSet,
238    kind: BindKind,
239}
240
241pub struct Checker<'a> {
242    diags: &'a mut Diagnostics,
243    subst: Subst,
244    types: BTreeMap<Arc<str>, TyDecl>,
245    schemes: BTreeMap<Arc<str>, Scheme>,
246    prims: BTreeMap<Arc<str>, (Prim, Scheme)>,
247    /// Innermost last. Resolution walks it backwards.
248    locals: Vec<Binding>,
249    globals: Vec<Binding>,
250    /// The row each definition's signature declares with `uses`.
251    declared: BTreeMap<Arc<str>, Row>,
252    /// `row Failure = raises(FormError), log` — a name for a bundle, expanded wherever it is used.
253    ///
254    /// Module-local by design. A `.becki` renders the expanded atoms, because a published contract
255    /// that referred to a name the reader has to look up somewhere else would not be a contract.
256    row_aliases: BTreeMap<Arc<str>, Row>,
257    identity: Option<IdentityDecl>,
258    /// Types declared in this module, in source order.
259    own_types: Vec<Arc<str>>,
260    /// The row *variable* standing for each definition's inferred row, minted before any body is
261    /// checked so that callers can name it and mutual recursion needs no ordering.
262    def_row: BTreeMap<Arc<str>, RowVarId>,
263    /// The row variables each definition's scheme quantifies over — §3.2's `e`, for a definition a
264    /// user wrote rather than one the prelude declares.
265    generic_rows: BTreeMap<Arc<str>, Vec<RowVarId>>,
266    /// What the body currently being checked has been seen to perform.
267    row: Row,
268    next_var: VarId,
269    /// Set while checking a fold's function, so §3.7's determinism rule can be enforced.
270    in_fold: bool,
271    /// The type parameters of the `def` whose signature or body is being read. Empty everywhere
272    /// else, which is why a monomorphic program cannot accidentally see one (`docs/32` §32.7).
273    typarams: BTreeSet<Arc<str>>,
274    /// The type parameters of the `model`, `union`, `newtype` or `type` whose fields are being
275    /// read, mapped to their position. Empty everywhere else, and never in scope at the same time
276    /// as `typarams`: a declaration has no body and a definition has no fields.
277    decl_typarams: BTreeMap<Arc<str>, u32>,
278    /// Every `trait` this module declares, by name.
279    traits: BTreeMap<Arc<str>, traits::TraitDecl>,
280    /// Which trait a method name belongs to. One entry per method, because a name may belong to
281    /// only one trait — see [`Checker::collect_traits`].
282    trait_methods: BTreeMap<Arc<str>, Arc<str>>,
283    /// The impls, keyed by trait and by the *head* constructor of the target type.
284    impls: BTreeMap<(Arc<str>, Arc<str>), traits::ImplDecl>,
285    /// The traits and impls *this* module declares, in order — an imported one is published by the
286    /// module that owns it, and republishing would make two modules claim the same contract.
287    own_traits: Vec<Arc<str>>,
288    own_impls: Vec<(Arc<str>, Arc<str>)>,
289    /// The mangled names `expand_impls` produced, so that a definition standing in for a trait
290    /// method can be told from one somebody wrote.
291    impl_methods: BTreeSet<Arc<str>>,
292    /// The dictionary parameters `expand_bounds` appended, per definition, in order. A call site
293    /// reads this to know how many of the callee's parameters it has to supply itself.
294    dicts: BTreeMap<Arc<str>, Vec<traits::DictParam>>,
295    /// How deep this pass is inside an expression or a type, against the ceiling the reader
296    /// counts against. The reader's bound does not cover this one: a macro can expand into a tree
297    /// deeper than the one that was written, and the checker is the first pass to see it.
298    nesting: Nesting,
299    /// How long a **flat** block this pass is inside, against its own ceiling.
300    ///
301    /// A second counter rather than a second use of `nesting`, because the two axes are not
302    /// comparable: 256 levels of nesting is pathological and 256 sequential bindings is merely a
303    /// long function. `block_from` recurses once per statement — a block is a chain of `Let`s in
304    /// `Core` whatever it looks like in source — so this is the recursion site, and counting it
305    /// here rather than at one grammar rule is [`docs/42`](../../../../../docs/42-security-assurance.md)
306    /// §42.2's Scriban lesson applied to the axis [`64`](../../../../../docs/64-compile-speed-report.md)
307    /// §64.4 found the ceiling did not cover.
308    block_nesting: Nesting,
309    /// The names a `parallel:` scope has bound so far, while its *later* children are being
310    /// checked. They are deliberately not in `locals` — a child that could see a sibling would
311    /// have to run after it — so this is what turns the resulting "cannot find" into the
312    /// diagnostic that says why the name is absent rather than that it is unknown.
313    parallel_siblings: Vec<Arc<str>>,
314    mode: Mode,
315}
316
317/// What kind of file is being checked.
318#[derive(Clone, Copy, Debug, PartialEq, Eq)]
319pub enum Mode {
320    /// An ordinary `.beck` module: every `def` needs a body.
321    Module,
322    /// A `.becki` interface (§3.6): every `def` is a signature, and none has a body.
323    Interface,
324}
325
326/// Check a module that macro expansion has already run over.
327pub fn check_module(module: &Node, diags: &mut Diagnostics) -> Program {
328    check_module_with(module, Mode::Module, &[], diags)
329}
330
331/// Check a module against the interfaces it imports — §3.6's separate compilation.
332///
333/// The importing module sees signatures and nothing else: types, parameter and result types,
334/// effect rows and placements. It never sees a body, which is exactly why editing one downstream
335/// costs nothing here.
336pub fn check_module_with(
337    module: &Node,
338    mode: Mode,
339    imports: &[(String, Interface)],
340    diags: &mut Diagnostics,
341) -> Program {
342    let name = module
343        .args
344        .first()
345        .and_then(|n| n.as_var())
346        .map(|s| s.as_str().to_string())
347        .unwrap_or_else(|| "main".into());
348
349    let mut ck = Checker {
350        diags,
351        subst: Subst::new(),
352        types: prelude::types(),
353        schemes: BTreeMap::new(),
354        prims: BTreeMap::new(),
355        locals: Vec::new(),
356        globals: Vec::new(),
357        declared: BTreeMap::new(),
358        row_aliases: BTreeMap::new(),
359        identity: None,
360        own_types: Vec::new(),
361        def_row: BTreeMap::new(),
362        row: Row::empty(),
363        next_var: 0,
364        in_fold: false,
365        typarams: BTreeSet::new(),
366        decl_typarams: BTreeMap::new(),
367        traits: BTreeMap::new(),
368        trait_methods: BTreeMap::new(),
369        impls: BTreeMap::new(),
370        own_traits: Vec::new(),
371        own_impls: Vec::new(),
372        impl_methods: BTreeSet::new(),
373        dicts: BTreeMap::new(),
374        generic_rows: BTreeMap::new(),
375        nesting: Nesting::new(),
376        block_nesting: Nesting::with_limit(beck_diag::depth::MAX_BLOCK),
377        parallel_siblings: Vec::new(),
378        mode,
379    };
380    for (name, prim, scheme) in prelude::prims() {
381        ck.prims.insert(Arc::from(name), (prim, scheme));
382        ck.globals.push(Binding {
383            name: Arc::from(name),
384            scopes: ScopeSet::empty(),
385            kind: BindKind::Prim(prim),
386        });
387    }
388
389    // The language's own traits, before anything local is read. `Num` is what `+`, `-`, `*` and `/`
390    // resolve through for a type that is neither `Int` nor `Float` nor `Str`, and it arrives by the
391    // same door an imported trait does — so nothing downstream has a special case for it.
392    ck.import_traits(&prelude::traits(), &[]);
393
394    // Imported names arrive before anything local is collected, so a local definition may shadow
395    // one and the diagnostic points at the local.
396    for (module_name, iface) in imports {
397        let (types, names) = iface.exports();
398        for (n, d) in types {
399            ck.types.insert(n, d);
400        }
401        ck.import_traits(&iface.traits, &iface.impls);
402        for (n, e) in names {
403            // A bounded import is given back the dictionary parameters the exporting module lowered
404            // it with, so a call site here supplies exactly what a call site there would.
405            let scheme = if e.bounds.is_empty() {
406                e.scheme
407            } else {
408                ck.import_bounded(&n, &e.bounds, e.scheme)
409            };
410            ck.schemes.insert(n.clone(), scheme);
411            ck.declared.insert(n.clone(), e.row);
412            ck.globals.push(Binding {
413                name: n.clone(),
414                scopes: ScopeSet::empty(),
415                kind: BindKind::Global(n),
416            });
417        }
418        let _ = module_name;
419    }
420
421    let items: Vec<&Node> = module.args.iter().skip(1).collect();
422    // Three passes over the declarations, and the split is what lets a type mention itself or
423    // anything declared later (docs/27 §27.3): names, then aliases in dependency order, then every
424    // declaration's field types against the complete set of names.
425    ck.declare_type_names(&items);
426    ck.collect_aliases(&items);
427    ck.collect_types(&items);
428    ck.register_type_constructors();
429    // Traits before impls, and impls before any signature: an `impl` is *desugared* into ordinary
430    // definitions, so by the time `collect_signatures` runs there is nothing trait-shaped left for
431    // it — or for placement, or for the splitter, or for the evaluator — to know about.
432    // Row aliases before anything reads a `uses` clause, and after the types so a `raises(E)`
433    // names a type that exists.
434    ck.collect_row_aliases(&items);
435    ck.collect_identity(&items);
436    ck.collect_traits(&items);
437    let expanded = ck.expand_impls(&items);
438    // A bounded `def` is rewritten in place, so what follows sees a definition with one more
439    // parameter and no bound. The rewrites are owned here because they replace items rather than
440    // adding to them.
441    let bounded: Vec<(usize, Node)> = items
442        .iter()
443        .enumerate()
444        .filter_map(|(i, it)| ck.expand_bounds(it).map(|n| (i, n)))
445        .collect();
446    // The impl's methods go **first**, and the order is load-bearing rather than tidy. A row is
447    // solved as its definition is checked, so a `try:` in a caller can only see what has already
448    // been decided — and a trait method is the one thing every operator call in the module goes
449    // through. Checking `Num::add@Money` before the definitions that write `a + b` is what lets a
450    // handler discharge the failure that impl performs rather than carrying it as an unresolved
451    // tail (`docs/47` §47.4).
452    let mut items: Vec<&Node> = expanded.iter().chain(items).collect();
453    for (i, node) in &bounded {
454        items[expanded.len() + *i] = node;
455    }
456    ck.collect_signatures(&items);
457    ck.collect_signal_names(&items);
458    let mut program = ck.check_items(&items, name);
459    program.imports = imports.iter().map(|(n, _)| n.clone()).collect();
460    program
461}
462
463mod exhaust;
464mod tests_in_beck;
465mod traits;
466
467pub use traits::is_impl_method;
468
469/// `a | b | c` parses as `(| (| a b) c)`; the alternatives are its leaves.
470fn flatten_alts(p: &Node, out: &mut Vec<Node>) {
471    if p.has_head("|") && p.args.len() == 2 {
472        flatten_alts(&p.args[0], out);
473        flatten_alts(&p.args[1], out);
474        return;
475    }
476    out.push(p.clone());
477}
478
479/// Point one alternative's binders at the variables the first alternative made.
480fn rename_binders(p: &mut Pattern, map: &BTreeMap<VarId, VarId>) {
481    match p {
482        Pattern::Wildcard | Pattern::Const(_) => {}
483        Pattern::Bind(v) => {
484            if let Some(&to) = map.get(v) {
485                *v = to;
486            }
487        }
488        Pattern::Ctor { binds, .. } => {
489            for (_, sub) in binds {
490                rename_binders(sub, map);
491            }
492        }
493        Pattern::List { items, rest } => {
494            for sub in items {
495                rename_binders(sub, map);
496            }
497            if let Some(Some(v)) = rest {
498                if let Some(&to) = map.get(v) {
499                    *v = to;
500                }
501            }
502        }
503        Pattern::Or(alts) => {
504            for sub in alts {
505                rename_binders(sub, map);
506            }
507        }
508        Pattern::At { var, inner } => {
509            if let Some(&to) = map.get(var) {
510                *var = to;
511            }
512            rename_binders(inner, map);
513        }
514    }
515}
516
517impl<'a> Checker<'a> {
518    fn error(&mut self, code: &'static str, msg: impl Into<String>, span: Span) {
519        self.diags.push(Diagnostic::error(code, msg, span));
520    }
521
522    fn fresh_var(&mut self) -> VarId {
523        let v = self.next_var;
524        self.next_var += 1;
525        v
526    }
527
528    /// Record that the body being checked performs this row.
529    fn perform(&mut self, row: &Row) {
530        let acc = std::mem::take(&mut self.row);
531        self.row = acc.union(row);
532    }
533
534    /// Check a sub-expression in its own effect scope, returning what it performed. Used for a
535    /// lambda body (whose effects belong to the lambda's *type*, not to its enclosing function) and
536    /// for each top-level item.
537    fn in_scope<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (T, Row) {
538        let outer = std::mem::take(&mut self.row);
539        let out = f(self);
540        let inner = std::mem::replace(&mut self.row, outer);
541        (out, inner)
542    }
543
544    // ------------------------------------------------------------------ declarations
545
546    /// Strip `@on(...)` decorators, returning the inner item and the tier it names.
547    fn undecorate<'n>(&mut self, item: &'n Node) -> (&'n Node, Option<(Tier, Span)>) {
548        let (inner, decos) = self.undecorate_full(item);
549        (inner, decos.tier)
550    }
551
552    /// The same, reporting every decorator a declaration may carry.
553    fn undecorate_full<'n>(&mut self, item: &'n Node) -> (&'n Node, Decorations) {
554        let mut inner = item;
555        let mut decos = Decorations::default();
556        while inner.is_form(sym::DECORATE) && inner.args.len() == 2 {
557            let deco = &inner.args[0];
558            let named = |d: &Node| d.args[0].as_var().map(|s| s.name.clone());
559            if deco.head_name() == Some("signal") && deco.args.is_empty() {
560                decos.declares_signal = true;
561            } else if deco.has_head(sym::ON) && deco.args.len() == 1 {
562                let span = deco.span();
563                match named(deco).and_then(|s| Tier::parse(&s)) {
564                    Some(t) => decos.tier = Some((t, span)),
565                    None => self.error(
566                        "B0300",
567                        format!("`{}` is not a tier", named(deco).unwrap_or(Arc::from("?"))),
568                        span,
569                    ),
570                }
571            } else if deco.has_head(sym::RENDER) && deco.args.len() == 1 {
572                let span = deco.span();
573                match named(deco).and_then(|s| render::Mode::parse(&s)) {
574                    Some(m) => decos.render = Some((m, span)),
575                    None => self.diags.push(
576                        Diagnostic::error(
577                            "B0306",
578                            format!(
579                                "`{}` is not a rendering mode",
580                                named(deco).unwrap_or(Arc::from("?"))
581                            ),
582                            span,
583                        )
584                        .with_fix("`@render(server)` for Mode A, `@render(client)` for Mode B"),
585                    ),
586                }
587            } else {
588                self.diags.push(
589                    Diagnostic::warning("B0301", "unsupported decorator", deco.span())
590                        .with_note("`@on(client|server|data|any)` and `@render(client|server)`"),
591                );
592            }
593            inner = &inner.args[1];
594        }
595        (inner, decos)
596    }
597
598    /// Register every declared type's *name* before resolving any declaration's field types.
599    ///
600    /// [`Checker::collect_types`] used to resolve each declaration as it walked the file, so a type
601    /// could only mention types declared above it, and could never mention itself:
602    ///
603    /// ```text
604    /// union Tree:
605    ///     Leaf(value: Int)
606    ///     Node(left: Tree, right: Tree)   error[B0310]: cannot find type `Tree`
607    /// ```
608    ///
609    /// which is `docs/25-benchmarks-and-expressiveness.md` §25.6 item 2 — §2.2 of SICP *is* "the
610    /// closure property", so this ended chapter 2 at §2.2 and took chapters 4 and 5 with it. It is
611    /// also the reason no Beck program could describe a tree, a comment thread or an expression.
612    ///
613    /// The fix is the one [`Checker::collect_signatures`] already made for definitions — "register
614    /// every top-level `def`'s signature before checking any body, so definitions may refer to each
615    /// other in any order" — applied one layer down. Names first, bodies second.
616    ///
617    /// Aliases are the exception and are resolved in between (see [`Checker::collect_aliases`]),
618    /// because they are *transparent*: `ty_from_node` replaces an alias with its target, so the
619    /// target has to be known before any declaration that mentions it is resolved.
620    fn declare_type_names(&mut self, items: &[&Node]) {
621        for item in items {
622            let (item, _) = self.undecorate(item);
623            let Some(name) = item
624                .args
625                .first()
626                .and_then(|n| n.as_var())
627                .map(|s| s.name.clone())
628            else {
629                continue;
630            };
631            if self.refuse_builtin_name(&name, item.span()) {
632                continue;
633            }
634            // The placeholder carries the declaration's *parameters*, unlike its fields: a
635            // recursive mention resolved while the real declaration is still being built —
636            // `Node(kids: list[Tree[T]])` — is arity-checked against this, so the placeholder has
637            // to know that `Tree` takes one argument even though it does not yet know what a
638            // `Tree` contains.
639            let params = Self::typaram_names(item);
640            // Otherwise a placeholder is never observed: `collect_types` overwrites every one of
641            // them, and a name that reaches a later pass unfilled would be a name no declaration
642            // produced.
643            let placeholder = if item.is_form(sym::MODEL) {
644                TyDecl::Model {
645                    name: name.clone(),
646                    params,
647                    fields: Vec::new(),
648                }
649            } else if item.is_form(sym::UNION) {
650                TyDecl::Union {
651                    name: name.clone(),
652                    params,
653                    variants: Vec::new(),
654                }
655            } else if item.is_form(sym::NEWTYPE) {
656                TyDecl::Newtype {
657                    name: name.clone(),
658                    params,
659                    inner: Ty::unit(),
660                }
661            } else {
662                // An alias is deliberately *not* registered here. `ty_from_node` expands an alias
663                // the moment it sees one, so registering a placeholder would expand every mention
664                // of it to the placeholder's target. `collect_aliases` fills them in next.
665                continue;
666            };
667            if self.types.insert(name.clone(), placeholder).is_some() {
668                self.error(
669                    "B0302",
670                    format!("type `{name}` is declared twice"),
671                    item.span(),
672                );
673            }
674            self.own_types.push(name.clone());
675        }
676    }
677
678    /// A declaration may not take a name the language already uses for a type.
679    ///
680    /// `Option` and `Result` were covered by [`Checker::collect_types`]'s "declared twice" check
681    /// only because they are prelude *declarations* and sit in `self.types`. The other fourteen —
682    /// `Int`, `Str`, `Bool`, `Float`, `Unit`, `Html`, `Attr`, `list`, `Map`, `Stream`, `Signal`,
683    /// `Envelope`, `secret` and `internal` — are builtin constructors that no declaration
684    /// registers, so a `model Int` was accepted and the name then meant the record in one
685    /// definition and the builtin in the next. [`Checker::bind_decl_typarams`] has refused the same
686    /// shadowing for a *type parameter* since `docs/36`; this is that rule at the production it was
687    /// missing (`docs/87` §87.4).
688    ///
689    /// Returns whether the name was refused, so the caller can skip registering it: leaving the
690    /// builtin in place is what keeps the rest of the module's errors about the module.
691    fn refuse_builtin_name(&mut self, name: &Arc<str>, span: Span) -> bool {
692        if prelude::builtin_arity(name).is_none() {
693            return false;
694        }
695        self.diags.push(
696            Diagnostic::error(
697                "B0317",
698                format!("`{name}` is already a type in the language"),
699                span,
700            )
701            .with_primary_label("this name is a builtin type")
702            .with_note(
703                "a declaration that took it would make every other mention in the module ambiguous \
704                 — the builtin in one signature and this declaration in the next",
705            ),
706        );
707        true
708    }
709
710    /// The names in a declaration's `(typarams …)` list, as written.
711    ///
712    /// Unvalidated on purpose: this runs while the set of type names is still being built, so it
713    /// cannot yet tell a parameter that shadows a type from one that does not.
714    /// [`Checker::bind_decl_typarams`] does that once, when every name is known.
715    fn typaram_names(item: &Node) -> Vec<Arc<str>> {
716        item.args
717            .get(1)
718            .filter(|n| n.is_form(sym::TYPARAMS))
719            .map(|n| {
720                n.args
721                    .iter()
722                    .filter_map(|p| p.as_var().map(|s| s.name.clone()))
723                    .collect()
724            })
725            .unwrap_or_default()
726    }
727
728    /// Put a *declaration's* type parameters in scope, numbered from [`ty::SCHEME_BASE`].
729    ///
730    /// This is the difference between a declaration and a definition. A `def`'s parameter is
731    /// **rigid** — `Ty::con("T")`, which unifies with itself and nothing else, so the body is
732    /// forced to work for every `T` (`docs/32` §32.7). A declaration has no body to constrain, and
733    /// its parameter has to survive into the stored `TyDecl` so that every later mention of
734    /// `Tree[Str]` can substitute for it. A positional variable does both: it cannot be unified
735    /// with by accident, because the checker's own variables are numbered from zero, and it is an
736    /// index into the arguments of whatever type mentions the name.
737    fn bind_decl_typarams(&mut self, item: &Node, decl_name: &str) -> Vec<Arc<str>> {
738        self.decl_typarams.clear();
739        let mut out: Vec<Arc<str>> = Vec::new();
740        let Some(list) = item.args.get(1).filter(|n| n.is_form(sym::TYPARAMS)) else {
741            return out;
742        };
743        for p in &list.args {
744            let Some(s) = p.as_var() else { continue };
745            let name = s.name.clone();
746            if self.types.contains_key(&name) || prelude::builtin_arity(&name).is_some() {
747                self.diags.push(
748                    Diagnostic::error(
749                        "B0314",
750                        format!(
751                            "`{name}` is already a type, so `{decl_name}` cannot take it as a \
752                             parameter"
753                        ),
754                        p.span(),
755                    )
756                    .with_primary_label("this name already names a type")
757                    .with_note(
758                        "a type parameter is a name the declaration invents, and one that shadowed \
759                         an existing type would make its fields read as though they mentioned that \
760                         type",
761                    ),
762                );
763                continue;
764            }
765            if out.contains(&name) {
766                self.error(
767                    "B0315",
768                    format!("`{name}` is repeated in `{decl_name}`'s type parameters"),
769                    p.span(),
770                );
771                continue;
772            }
773            self.decl_typarams
774                .insert(name.clone(), ty::SCHEME_BASE + out.len() as u32);
775            out.push(name);
776        }
777        out
778    }
779
780    /// Resolve `type` aliases, in dependency order, before anything else reads a type.
781    ///
782    /// An alias is transparent, so it must be *expanded* rather than referenced, and expanding it
783    /// needs its target. Ordering that by hand would put the burden back on the source order this
784    /// pass exists to remove, so instead each alias is resolved on demand and the ones it names are
785    /// resolved first.
786    ///
787    /// A cycle is the one case that cannot be resolved rather than merely reordered: `type A = B`
788    /// and `type B = A` describe no type at all, and `type Chain = list[Chain]` is an infinitely
789    /// large one. A *union* may be recursive because its variants are a finite tag plus fields; an
790    /// alias has no such boundary, which is why the two are different passes and only one of them
791    /// refuses a cycle.
792    fn collect_aliases(&mut self, items: &[&Node]) {
793        let mut pending: BTreeMap<Arc<str>, (Node, Span)> = BTreeMap::new();
794        let mut order: Vec<Arc<str>> = Vec::new();
795        for item in items {
796            let (item, _) = self.undecorate(item);
797            if !item.is_form(sym::TYPE) || item.args.len() < 3 {
798                continue;
799            }
800            let Some(name) = item.args[0].as_var().map(|s| s.name.clone()) else {
801                continue;
802            };
803            if self.types.contains_key(&name) || pending.contains_key(&name) {
804                self.error(
805                    "B0302",
806                    format!("type `{name}` is declared twice"),
807                    item.span(),
808                );
809                continue;
810            }
811            // Already reported by `declare_type_names`, which walks every declaration including
812            // this one. Skipped rather than reported twice, and skipped rather than registered so
813            // that `Int` still means `Int` in the rest of the module's diagnostics.
814            if prelude::builtin_arity(&name).is_some() {
815                continue;
816            }
817            // The whole item, not just its target: an alias may be parameterised, and its
818            // parameters have to be in scope when the target is read.
819            pending.insert(name.clone(), (item.clone(), item.span()));
820            order.push(name);
821        }
822        let mut resolving: Vec<Arc<str>> = Vec::new();
823        for name in order {
824            self.resolve_alias(&name, &pending, &mut resolving);
825        }
826    }
827
828    fn resolve_alias(
829        &mut self,
830        name: &Arc<str>,
831        pending: &BTreeMap<Arc<str>, (Node, Span)>,
832        resolving: &mut Vec<Arc<str>>,
833    ) {
834        if self.types.contains_key(name) {
835            return;
836        }
837        let Some((item, span)) = pending.get(name) else {
838            return;
839        };
840        let (item, span) = (item.clone(), *span);
841        let node = &item.args[2];
842        if resolving.contains(name) {
843            self.error(
844                "B0312",
845                format!(
846                    "type alias `{name}` is defined in terms of itself — an alias is transparent, \
847                     so this describes no type; a `union` may be recursive, an alias may not"
848                ),
849                span,
850            );
851            // Registered as an alias for a fresh variable, so that every *other* mention of it
852            // reports nothing further: one cycle is one diagnostic.
853            let ty = self.subst.fresh();
854            self.types.insert(
855                name.clone(),
856                TyDecl::Alias {
857                    name: name.clone(),
858                    params: Self::typaram_names(&item),
859                    ty,
860                },
861            );
862            return;
863        }
864        resolving.push(name.clone());
865        for referenced in Self::type_names_in(node) {
866            if pending.contains_key(&referenced) {
867                self.resolve_alias(&referenced, pending, resolving);
868            }
869        }
870        resolving.pop();
871        if self.types.contains_key(name) {
872            return; // the cycle branch above already filled it in
873        }
874        // Bound *after* the aliases this one names are resolved, because resolving them rebinds
875        // the same scope.
876        let params = self.bind_decl_typarams(&item, name);
877        let ty = self.ty_from_node(node);
878        self.decl_typarams.clear();
879        self.types.insert(
880            name.clone(),
881            TyDecl::Alias {
882                name: name.clone(),
883                params,
884                ty,
885            },
886        );
887        self.own_types.push(name.clone());
888    }
889
890    /// Every type name a type expression mentions, so an alias can be resolved after the aliases it
891    /// names.
892    ///
893    /// Deliberately over-approximate: it reports the head of every application, including builtins like
894    /// `list`, and the caller keeps only the ones that are pending aliases. A name it missed would be an
895    /// alias resolved too early, which is why it errs the other way.
896    fn type_names_in(n: &Node) -> Vec<Arc<str>> {
897        let mut out = Vec::new();
898        fn walk(n: &Node, out: &mut Vec<Arc<str>>) {
899            if let Some(name) = n.head_name() {
900                out.push(Arc::from(name));
901            }
902            for a in &n.args {
903                walk(a, out);
904            }
905        }
906        walk(n, &mut out);
907        out
908    }
909
910    fn collect_types(&mut self, items: &[&Node]) {
911        for item in items {
912            let (item, _) = self.undecorate(item);
913            let Some(name) = item
914                .args
915                .first()
916                .and_then(|n| n.as_var())
917                .map(|s| s.name.clone())
918            else {
919                continue;
920            };
921            if !item.is_form(sym::MODEL) && !item.is_form(sym::UNION) && !item.is_form(sym::NEWTYPE)
922            {
923                // `type` aliases were resolved by `collect_aliases`, and everything else is not a
924                // type declaration.
925                continue;
926            }
927            // A declaration has no body, so a bound on one is a promise with no reader: nothing
928            // inside a `model` or a `union` can call a method. Refused rather than ignored.
929            for (p, _) in traits::bounds_of(&item.args[1]) {
930                self.diags.push(
931                    Diagnostic::error(
932                        "B0316",
933                        format!("`{name}` cannot bound its type parameter `{p}`"),
934                        item.args[1].span(),
935                    )
936                    .with_note(
937                        "a bound says what a body may call, and a declaration has no body; the \
938                         definitions that take this type apart are where the bound belongs",
939                    ),
940                );
941            }
942            // In scope for every field type below, and only for those: a parameter belongs to the
943            // declaration that introduced it.
944            let params = self.bind_decl_typarams(item, &name);
945            let decl = if item.is_form(sym::MODEL) {
946                let fields = item.args[2..]
947                    .iter()
948                    .filter_map(|f| self.field_decl(f))
949                    .collect();
950                TyDecl::Model {
951                    name: name.clone(),
952                    params,
953                    fields,
954                }
955            } else if item.is_form(sym::UNION) {
956                let variants = item.args[2..]
957                    .iter()
958                    .map(|vn| Variant {
959                        name: vn
960                            .args
961                            .first()
962                            .and_then(|n| n.as_var())
963                            .map(|s| s.name.clone())
964                            .unwrap_or_else(|| Arc::from("?")),
965                        fields: vn.args[1..]
966                            .iter()
967                            .filter_map(|f| self.field_decl(f))
968                            .collect(),
969                    })
970                    .collect();
971                TyDecl::Union {
972                    name: name.clone(),
973                    params,
974                    variants,
975                }
976            } else {
977                TyDecl::Newtype {
978                    name: name.clone(),
979                    params,
980                    inner: self.ty_from_node(&item.args[2]),
981                }
982            };
983            self.decl_typarams.clear();
984            // Overwrites the placeholder `declare_type_names` left. Duplicates were reported there,
985            // where both declarations are still in view; reporting them again here would double
986            // every message.
987            self.types.insert(name.clone(), decl);
988        }
989    }
990
991    fn field_decl(&mut self, f: &Node) -> Option<(Arc<str>, Ty)> {
992        if !f.is_form(sym::FIELD) || f.args.len() != 2 {
993            return None;
994        }
995        let name = f.args[0].as_var()?.name.clone();
996        Some((name, self.ty_from_node(&f.args[1])))
997    }
998
999    fn register_type_constructors(&mut self) {
1000        let decls: Vec<TyDecl> = self.types.values().cloned().collect();
1001        for d in decls {
1002            match &d {
1003                TyDecl::Union { name, variants, .. } => {
1004                    for v in variants {
1005                        self.globals.push(Binding {
1006                            name: v.name.clone(),
1007                            scopes: ScopeSet::empty(),
1008                            kind: BindKind::Ctor(name.clone(), v.name.clone()),
1009                        });
1010                    }
1011                }
1012                TyDecl::Model { name, .. } | TyDecl::Newtype { name, .. } => {
1013                    self.globals.push(Binding {
1014                        name: name.clone(),
1015                        scopes: ScopeSet::empty(),
1016                        kind: BindKind::Model(name.clone()),
1017                    });
1018                }
1019                TyDecl::Alias { .. } => {}
1020            }
1021        }
1022    }
1023
1024    /// Register every top-level `def`'s signature before checking any body, so definitions may
1025    /// refer to each other in any order.
1026    fn collect_signatures(&mut self, items: &[&Node]) {
1027        for item in items {
1028            let (item, _) = self.undecorate(item);
1029            if !item.is_form(sym::DEF) || item.args.len() < 5 {
1030                continue;
1031            }
1032            let Some(name) = item.args[0].as_var().map(|s| s.name.clone()) else {
1033                continue;
1034            };
1035            // The type parameters go into scope *before* the signature is read, so that `T` in
1036            // `xs: list[T]` resolves to the rigid `T` rather than to `cannot find type`.
1037            let typarams = self.bind_typarams(&item.args[1], &name);
1038            let params: Vec<Ty> = item.args[2]
1039                .args
1040                .iter()
1041                .map(|p| {
1042                    if p.is_form(sym::ANNOT) && p.args.len() == 2 {
1043                        self.ty_from_node(&p.args[1])
1044                    } else {
1045                        self.error(
1046                            "B0303",
1047                            "a top-level parameter needs a type annotation",
1048                            p.span(),
1049                        );
1050                        self.subst.fresh()
1051                    }
1052                })
1053                .collect();
1054            let ret = match item.args[3].args.first() {
1055                Some(t) => self.ty_from_node(t),
1056                None => {
1057                    self.error(
1058                        "B0304",
1059                        format!("`{name}` needs a return type"),
1060                        item.args[0].span(),
1061                    );
1062                    self.subst.fresh()
1063                }
1064            };
1065            self.typarams.clear();
1066            // `record(x)` is a record literal however `record` is bound, so a definition with one
1067            // of these names would compile and never be reachable. Better a message than a mystery.
1068            if sym::RESERVED_FORMS.contains(&name.as_ref()) {
1069                self.diags.push(
1070                    Diagnostic::error(
1071                        "B0312",
1072                        format!("`{name}` is a form of the language, so nothing can be named it"),
1073                        item.args[0].span(),
1074                    )
1075                    .with_primary_label("this name is matched as syntax before it is resolved")
1076                    .with_note(
1077                        "the checker recognises these heads structurally, so a definition with one \
1078                         of their names would be shadowed by the form and never called",
1079                    ),
1080                );
1081            }
1082            let declared = self.declared_row(item.args.get(4));
1083
1084            // The definition's latent row is a *variable*, bound once its body has been checked.
1085            // Minting it here is what lets any definition call any other in any order.
1086            let rv = self.subst.fresh_row_var();
1087            // §3.2's `map : (list[a], (a -> b ! e)) -> list[b] ! e`, for a definition a *user*
1088            // wrote. The row variables the signature's function-typed parameters carry are
1089            // quantified, so each call site gets its own — without which one caller passing an
1090            // effectful function makes every other caller effectful too (`docs/33` §33.2).
1091            let generic_rows = self.generalisable_rows(&params, &ret);
1092            let mut latent = Row::var(rv);
1093            latent.tails.extend(generic_rows.iter().copied());
1094            self.schemes.insert(
1095                name.clone(),
1096                Scheme {
1097                    vars: Vec::new(),
1098                    row_vars: generic_rows.clone(),
1099                    params: typarams,
1100                    ty: Ty::fun_eff(params, ret, latent),
1101                },
1102            );
1103            self.def_row.insert(name.clone(), rv);
1104            self.generic_rows.insert(name.clone(), generic_rows);
1105            self.declared.insert(name.clone(), declared);
1106            self.globals.push(Binding {
1107                name: name.clone(),
1108                scopes: ScopeSet::empty(),
1109                kind: BindKind::Global(name.clone()),
1110            });
1111        }
1112    }
1113
1114    /// The row variables a definition's signature may quantify over.
1115    ///
1116    /// Those written into its *parameters*, and only when the return type carries none of its own.
1117    /// The restriction is not conservatism for its own sake: a variable quantified in the scheme is
1118    /// renamed by `instantiate` wherever it appears **syntactically**, and a return type whose row
1119    /// is bound — through the substitution — to a parameter's would keep the generic variable on
1120    /// one side of the call and the fresh one on the other. `docs/33` §33.3 says what that costs
1121    /// and what would lift it.
1122    fn generalisable_rows(&self, params: &[Ty], ret: &Ty) -> Vec<RowVarId> {
1123        let mut in_ret = Vec::new();
1124        row_vars_of(ret, &mut in_ret);
1125        if !in_ret.is_empty() {
1126            return Vec::new();
1127        }
1128        let mut out = Vec::new();
1129        for p in params {
1130            row_vars_of(p, &mut out);
1131        }
1132        out.sort_unstable();
1133        out.dedup();
1134        out
1135    }
1136
1137    /// Put a `def`'s `[T, U]` into scope, and answer with the names in order.
1138    ///
1139    /// Order matters because it is the order [`Subst::instantiate`] and `beck iface` both use, and
1140    /// a set would not have one. Shadowing is refused rather than resolved: a type parameter named
1141    /// after a `model` in the same module is far more likely to be a mistake than an intention, and
1142    /// there is no syntax to disambiguate it afterwards.
1143    fn bind_typarams(&mut self, node: &Node, def_name: &str) -> Vec<Arc<str>> {
1144        self.typarams.clear();
1145        let mut out: Vec<Arc<str>> = Vec::new();
1146        for p in &node.args {
1147            let Some(name) = traits::typaram_name(p) else {
1148                continue;
1149            };
1150            if self.types.contains_key(&name) || prelude::builtin_arity(&name).is_some() {
1151                self.diags.push(
1152                    Diagnostic::error(
1153                        "B0314",
1154                        format!("`{name}` is already a type, so `{def_name}` cannot take it as a parameter"),
1155                        p.span(),
1156                    )
1157                    .with_primary_label("this name already names a type")
1158                    .with_note(
1159                        "a type parameter is a name the definition invents, and one that shadowed \
1160                         an existing type would make its signature read as though it mentioned that \
1161                         type",
1162                    ),
1163                );
1164                continue;
1165            }
1166            if out.contains(&name) {
1167                self.error(
1168                    "B0315",
1169                    format!("`{name}` is repeated in `{def_name}`'s type parameters"),
1170                    p.span(),
1171                );
1172                continue;
1173            }
1174            out.push(name.clone());
1175            self.typarams.insert(name);
1176        }
1177        out
1178    }
1179
1180    /// The row a `uses` clause declares. §3.2's atoms are written as they print:
1181    /// `durable`, `net.out(api.example.com)`, `cap.session`.
1182    fn declared_row(&mut self, uses: Option<&Node>) -> Row {
1183        let mut row = Row::empty();
1184        let Some(u) = uses else { return row };
1185        for e in &u.args {
1186            // `net.out(host)` and `cap.session` are ordinary dotted syntax by the time they reach
1187            // here, so the atom is reassembled from what was written rather than pattern-matched
1188            // per shape — which is why adding an atom to §3.2's list costs one line in `row.rs`.
1189            let text = written_form(e).unwrap_or_default();
1190            // A name that is not an atom may be a row alias. Tried second, so an alias cannot
1191            // shadow an effect: `row durable = ...` would otherwise silently change what every
1192            // signature in the module means.
1193            if let Some(atom) = Effect::parse(&text) {
1194                row.add(atom);
1195            } else if let Some(alias) = self.row_aliases.get(text.as_str()).cloned() {
1196                row = row.union(&alias);
1197            } else {
1198                let mut d = Diagnostic::error(
1199                    "B0305",
1200                    format!(
1201                        "`{}` is neither an effect nor a row",
1202                        if text.is_empty() { "?" } else { &text }
1203                    ),
1204                    e.span(),
1205                );
1206                // `fs(path)` was one atom until `docs/81` split it, and it is the one spelling a
1207                // reader is likely to arrive with — from §3.2 as it stood, or from a habit. Saying
1208                // which of the two to write is more use than saying the name is unknown.
1209                if let Some(path) = text.strip_prefix("fs(").and_then(|r| r.strip_suffix(')')) {
1210                    d = d.with_note(format!(
1211                        "`fs` is two atoms: write `fs.read({path})` or `fs.write({path})`. One \
1212                         name for both could not say whether a mount needs to be writable, or \
1213                         whether two children of a `parallel:` scope may touch it at once"
1214                    ));
1215                }
1216                self.diags.push(d);
1217            }
1218        }
1219        row
1220    }
1221
1222    /// Collect `row Name = …` declarations, before any signature mentions one.
1223    ///
1224    /// An alias may name an alias declared earlier in the file. It may not name one declared later,
1225    /// and that is the one place this differs from types — which may mention anything, in any order
1226    /// (`docs/27` §27.3). The reason is that a row is a *set* being built here rather than a
1227    /// declaration being resolved later, and a forward reference would mean a fixpoint over
1228    /// something a reader cannot see the end of. A cycle is refused for the same reason.
1229    fn collect_row_aliases(&mut self, items: &[&Node]) {
1230        for item in items {
1231            let (item, _) = self.undecorate(item);
1232            if !item.is_form(sym::ROW) || item.args.len() < 2 {
1233                continue;
1234            }
1235            let Some(name) = item.args[0].as_var().map(|s| s.name.clone()) else {
1236                continue;
1237            };
1238            if self.row_aliases.contains_key(&name) {
1239                self.error(
1240                    "B0394",
1241                    format!("row `{name}` is declared twice"),
1242                    item.span(),
1243                );
1244                continue;
1245            }
1246            let body = Node::form("uses", item.args[1..].to_vec(), item.span());
1247            let row = self.declared_row(Some(&body));
1248            self.row_aliases.insert(name, row);
1249        }
1250    }
1251
1252    /// Read `identity = external(issuer="…")` — D6's block, as a declaration.
1253    ///
1254    /// Everything here is checked at compile time because everything here is read at compile time:
1255    /// [`crate::Placed`] carries it, `beck-infra` turns the host into an egress rule, and `beck run`
1256    /// builds the relying party from the same string. A URL that only fails at startup would be a
1257    /// deployment that builds and cannot authenticate anybody.
1258    fn collect_identity(&mut self, items: &[&Node]) {
1259        for item in items {
1260            let (item, _) = self.undecorate(item);
1261            if !item.is_form(sym::IDENTITY) || item.args.len() != 1 {
1262                continue;
1263            }
1264            let span = item.span();
1265            if self.identity.is_some() {
1266                self.error("B0359", "identity is declared twice", span);
1267                continue;
1268            }
1269            // D6's two forms. `managed()` carries nothing, because the issuer is a Service the
1270            // deployment has not named yet (§6.5), and `external` carries the URL because nothing
1271            // in this repository is going to provision somebody else's provider.
1272            let call = &item.args[0];
1273            if call.is_form("managed") {
1274                if !call.args.is_empty() {
1275                    self.error("B0359", "`managed()` takes no arguments", span);
1276                    continue;
1277                }
1278                self.identity = Some(IdentityDecl::Managed { span });
1279                continue;
1280            }
1281            if !call.is_form("external") {
1282                self.error(
1283                    "B0359",
1284                    "`external(issuer=\"…\")` and `managed()` are the identity providers",
1285                    span,
1286                );
1287                continue;
1288            }
1289            // `issuer=` by name rather than by position, because a second argument will be added
1290            // one day and a positional URL would move.
1291            let issuer: Option<String> = call.args.iter().find_map(|a| {
1292                let named = a.is_form(sym::KW_ARG)
1293                    && a.args.first().and_then(|n| n.as_var()).map(|v| &*v.name) == Some("issuer");
1294                if !named {
1295                    return None;
1296                }
1297                match a.args.get(1).and_then(|v| v.as_lit()) {
1298                    Some(beck_syntax::Lit::Str(s)) => Some(s.to_string()),
1299                    _ => None,
1300                }
1301            });
1302            let Some(issuer) = issuer else {
1303                self.error(
1304                    "B0359",
1305                    "`external` needs `issuer=\"https://…\"`, written as a literal",
1306                    span,
1307                );
1308                continue;
1309            };
1310            let Some(host) = issuer_host(&issuer) else {
1311                self.error(
1312                    "B0359",
1313                    format!("`{issuer}` is not an https URL whose host an egress rule could name"),
1314                    span,
1315                );
1316                continue;
1317            };
1318            self.identity = Some(IdentityDecl::External {
1319                issuer: Arc::from(issuer.trim_end_matches('/')),
1320                host: Arc::from(host.as_str()),
1321                span,
1322            });
1323        }
1324    }
1325
1326    /// Register every top-level signal before checking any of them.
1327    ///
1328    /// The signal graph is legitimately *cyclic*: `events` is decided from `todos`, and `todos` is
1329    /// folded from `events`. §3.7 makes that sound — validation reads the accumulator under the
1330    /// same lock as the append — so the checker must not require a topological order the program
1331    /// does not have.
1332    fn collect_signal_names(&mut self, items: &[&Node]) {
1333        for item in items {
1334            let (item, _) = self.undecorate(item);
1335            if !(item.is_form(sym::LET) || item.is_form(sym::VAR)) || item.args.len() != 2 {
1336                continue;
1337            }
1338            let target = &item.args[0];
1339            let (name_node, annot) = if target.is_form(sym::ANNOT) && target.args.len() == 2 {
1340                (&target.args[0], Some(&target.args[1]))
1341            } else {
1342                (target, None)
1343            };
1344            let Some(s) = name_node.as_var() else {
1345                continue;
1346            };
1347            let ty = match annot {
1348                Some(t) => self.ty_from_node(t),
1349                None => self.subst.fresh(),
1350            };
1351            self.schemes.insert(s.name.clone(), Scheme::mono(ty));
1352            self.globals.push(Binding {
1353                name: s.name.clone(),
1354                scopes: s.scopes.clone(),
1355                kind: BindKind::Global(s.name.clone()),
1356            });
1357        }
1358    }
1359
1360    fn check_items(mut self, items: &[&Node], name: String) -> Program {
1361        let docs = crate::docgen::collect_docs(items);
1362        let mut defs = BTreeMap::new();
1363        let mut def_order = Vec::new();
1364        let mut signals = Vec::new();
1365        let mut test_items: Vec<&Node> = Vec::new();
1366
1367        for item in items {
1368            let (inner, decos) = self.undecorate_full(item);
1369            let declares_signal = decos.declares_signal;
1370            let tier_is_annotated = decos.tier.is_some();
1371            let (tier, tier_span) = decos.tier.unwrap_or((Tier::Any, inner.span()));
1372
1373            // Where a component renders is a question about a component, and a component is a
1374            // signal. On anything else the decorator would be read by nobody, which is worse than
1375            // being refused.
1376            if let Some((_, span)) = decos.render {
1377                if !(inner.is_form(sym::LET) || inner.is_form(sym::VAR)) {
1378                    self.diags.push(
1379                        Diagnostic::error(
1380                            "B0405",
1381                            "only a component can say where it renders",
1382                            span,
1383                        )
1384                        .with_primary_label("`@render` belongs on a `Signal[Html]` declaration")
1385                        .with_note(
1386                            "A definition is unplaced code, compiled to every tier that needs it \
1387                             (§3.3). What renders where is decided per component, which is what a \
1388                             page signal is.",
1389                        ),
1390                    );
1391                }
1392            }
1393
1394            if inner.is_form(sym::DEF) {
1395                if let Some(def) =
1396                    self.check_def(inner, tier, tier_span, tier_is_annotated, declares_signal)
1397                {
1398                    def_order.push(def.name.clone());
1399                    defs.insert(def.name.clone(), def);
1400                }
1401            } else if inner.is_form(sym::LET) || inner.is_form(sym::VAR) {
1402                if let Some(mut s) = self.check_signal(inner, tier, tier_span, tier_is_annotated) {
1403                    s.render = decos.render;
1404                    signals.push(s);
1405                }
1406            } else if inner.is_form(sym::TEST) || inner.is_form(sym::PROPERTY) {
1407                // Deferred: a test's clauses are typed against the state and event types, which are
1408                // only known once every signal has been checked. §21.2's "the log is the state" is
1409                // exactly why — a `given` is a `list[Event]`, and `Event` is whatever the program's
1410                // own `decide` node produces.
1411                test_items.push(inner);
1412            } else if inner.is_form(sym::MODEL)
1413                || inner.is_form(sym::UNION)
1414                || inner.is_form(sym::TYPE)
1415                || inner.is_form(sym::NEWTYPE)
1416                || inner.is_form(sym::IMPORT)
1417                || inner.is_form(sym::TRAIT)
1418                || inner.is_form(sym::IMPL)
1419                || inner.is_form(sym::ROW)
1420                || inner.is_form(sym::IDENTITY)
1421            {
1422                // Declarations, all of them already collected. A `trait` was read by
1423                // `collect_traits` and an `impl` was expanded into the `def`s this loop is
1424                // checking, so neither has anything left to do here.
1425            } else {
1426                self.error("B0307", "unsupported top-level item", inner.span());
1427            }
1428        }
1429
1430        // §21.2's `test` and `property` blocks, now that every signal and definition has been seen.
1431        //
1432        // A program that declares signals but has no `decide`/`durable(fold(…))` is one the
1433        // splitter refuses by name (B0500–B0504), and that refusal is the diagnostic worth reading.
1434        // Type-checking its tests first would bury it under one error per clause, so the tests are
1435        // dropped here and the later stage speaks. A module with *no* signals is a different case —
1436        // a library, which the project pipeline checks on purpose — and there B0706 is the answer.
1437        let subjects = self.test_subjects(&signals, &defs);
1438        let broken_topology =
1439            !signals.is_empty() && (subjects.state.is_none() || subjects.event.is_none());
1440        let mut tests = Vec::new();
1441        if !broken_topology {
1442            for item in test_items {
1443                if let Some(t) = self.check_test(item, &subjects, &defs) {
1444                    tests.push(t);
1445                }
1446            }
1447        }
1448
1449        // Resolve every recorded type through the substitution so that what leaves the checker is
1450        // ground wherever inference succeeded. Rows resolve here too, and only here: a row bound
1451        // during one body may mention a variable another body binds later, so nothing is final
1452        // until every body has been seen.
1453        for def in defs.values_mut() {
1454            def.ret = self.subst.resolve(&def.ret);
1455            for p in &mut def.params {
1456                p.2 = self.subst.resolve(&p.2);
1457            }
1458            resolve_types(&mut def.body, &self.subst);
1459            def.row = self.subst.resolve_row(&def.row);
1460            // Close the row variables no caller can ever bind.
1461            //
1462            // A free tail means "plus whatever the caller's function argument does", which is only
1463            // a real quantity when the definition *takes* a function. `mine(s: State, session:
1464            // Session)` calls `sort_by`, whose scheme is row-polymorphic, and subsumption leaves a
1465            // fresh trailing variable behind so a later call site could widen it. For a definition
1466            // with no function parameter there is no later call site: the variable is vacuous, and
1467            // printing `{e9 | e10}` where the truth is `{}` would make every pure function in
1468            // `beck explain place` look effectful.
1469            let mut bindable = Vec::new();
1470            for (_, _, t) in &def.params {
1471                self.subst.free_row_vars(t, &mut bindable);
1472            }
1473            def.row.tails.retain(|v| bindable.contains(v));
1474            def.effects = def.row.atoms.iter().cloned().collect();
1475        }
1476        for t in &mut tests {
1477            for clause in &mut t.clauses {
1478                for c in clause_cores_mut(clause) {
1479                    resolve_types(c, &self.subst);
1480                }
1481            }
1482            for p in &mut t.params {
1483                p.2 = self.subst.resolve(&p.2);
1484            }
1485        }
1486        for s in &mut signals {
1487            s.ty = self.subst.resolve(&s.ty);
1488            resolve_types(&mut s.expr, &self.subst);
1489            s.row = self.subst.resolve_row(&s.row);
1490            // A signal takes no arguments, so nothing can widen its row: every free variable in it
1491            // is vacuous.
1492            s.row.tails.clear();
1493            s.effects = s.row.atoms.iter().cloned().collect();
1494        }
1495
1496        // §3.6: "effect widening is a breaking API change". A `uses` clause is therefore a *bound*,
1497        // and a body that exceeds it is an error rather than a silent widening of the signature —
1498        // which is the property that makes "a library that starts phoning home cannot do so
1499        // silently" true of Beck rather than aspirational.
1500        for name in &def_order {
1501            let Some(def) = defs.get(name) else { continue };
1502            if !def.row_is_declared {
1503                continue;
1504            }
1505            let undeclared: Vec<Effect> = def
1506                .effects
1507                .iter()
1508                .filter(|e| !e.is_ambient() && !def.declared_effects.contains(e))
1509                .cloned()
1510                .collect();
1511            if undeclared.is_empty() {
1512                continue;
1513            }
1514            let names: Vec<String> = undeclared.iter().map(|e| e.name()).collect();
1515            self.diags.push(
1516                Diagnostic::error(
1517                    "B0370",
1518                    format!("`{name}` performs more than its signature declares"),
1519                    def.span,
1520                )
1521                .with_primary_label(format!("undeclared: {}", names.join(", ")))
1522                .with_note(
1523                    "a `uses` clause is the published bound, and widening it is a breaking API \
1524                     change — so the compiler will not widen it for you",
1525                )
1526                .with_fix(format!(
1527                    "declare it: `uses {}`",
1528                    def.effects
1529                        .iter()
1530                        .filter(|e| !e.is_ambient())
1531                        .map(|e| e.name())
1532                        .collect::<Vec<_>>()
1533                        .join(", ")
1534                )),
1535            );
1536        }
1537
1538        // Declaration order, so a rendered `.becki` is stable and a trait a later impl names has
1539        // already been read by the time the file reaches it again.
1540        let traits: Vec<ty::TraitSig> = self
1541            .own_traits
1542            .iter()
1543            .filter_map(|n| self.traits.get(n).map(|d| d.sig.clone()))
1544            .collect();
1545        // The header, plus what each of its methods turned out to perform. An impl's row is
1546        // inferred rather than taken from the trait (`docs/47`), so a module that publishes an
1547        // impl has to publish the rows too — a caller in another module has nowhere else to get
1548        // them, and taking them off the trait is exactly the unsoundness this closes.
1549        let impls: Vec<ty::ImplSig> = self
1550            .own_impls
1551            .iter()
1552            .filter_map(|k| self.impls.get(k).map(|d| d.sig.clone()))
1553            .map(|mut sig| {
1554                let head = sig.head();
1555                if let Some(decl) = self.traits.get(&sig.trait_name) {
1556                    for m in &decl.sig.methods {
1557                        let mangled = traits::mangle(&sig.trait_name, &m.name, &head);
1558                        let Some(def) = defs.get(&mangled) else {
1559                            continue;
1560                        };
1561                        let row: Vec<Effect> = def
1562                            .effects
1563                            .iter()
1564                            .filter(|e| !e.is_ambient())
1565                            .cloned()
1566                            .collect();
1567                        if !row.is_empty() {
1568                            sig.effects.push((m.name.clone(), row));
1569                        }
1570                    }
1571                }
1572                sig
1573            })
1574            .collect();
1575
1576        Program {
1577            name,
1578            types: self.types,
1579            traits,
1580            impls,
1581            own_types: self.own_types,
1582            imports: Vec::new(),
1583            defs,
1584            def_order,
1585            signals,
1586            tests,
1587            docs,
1588            identity: self.identity,
1589        }
1590    }
1591
1592    // ------------------------------------------------------------------ §21.2's test construct
1593
1594    /// The four types a test's clauses are checked against, read off the program's own signal graph.
1595    ///
1596    /// Nothing here is a convention a test author has to know: `given` is a `list[Event]` because
1597    /// the fold's stream is a `Stream[Event]`, and `result` is `validate`'s return type because
1598    /// `when` goes through `validate`. A program with no merge point has none of them, and saying
1599    /// so once here is better than four confusing type errors later.
1600    fn check_def(
1601        &mut self,
1602        item: &Node,
1603        tier: Tier,
1604        tier_span: Span,
1605        tier_is_annotated: bool,
1606        declares_signal: bool,
1607    ) -> Option<Def> {
1608        let name = item.args[0].as_var()?.name.clone();
1609        let scheme = self.schemes.get(&name)?.clone();
1610        let Ty::Fun(param_tys, ret, latent) = scheme.ty.clone() else {
1611            return None;
1612        };
1613        // The same rigid names the signature was read with, so an annotation *inside* the body may
1614        // mention them too — and so that a diagnostic about one prints `T` and not `?7`.
1615        self.typarams = scheme.params.iter().cloned().collect();
1616
1617        let before = self.locals.len();
1618        let mut params = Vec::new();
1619        for (p, ty) in item.args[2].args.iter().zip(&param_tys) {
1620            let target = if p.is_form(sym::ANNOT) { &p.args[0] } else { p };
1621            let Some(s) = target.as_var() else { continue };
1622            let id = self.fresh_var();
1623            params.push((id, s.name.clone(), ty.clone()));
1624            self.locals.push(Binding {
1625                name: s.name.clone(),
1626                scopes: s.scopes.clone(),
1627                kind: BindKind::Local(id, ty.clone()),
1628            });
1629        }
1630
1631        let body_node = item.args.get(5);
1632        let span = item.span();
1633        // A `def` with no body is a signature. That is the whole content of a `.becki` (§3.6), and
1634        // it is a promise nobody keeps in an ordinary module.
1635        if body_node.is_none() && self.mode == Mode::Module {
1636            self.diags.push(
1637                Diagnostic::error("B0335", format!("`{name}` has no body"), span)
1638                    .with_primary_label("a signature with nothing behind it")
1639                    .with_note(
1640                        "a bodyless `def` is a declaration, which is what a `.becki` interface file \
1641                         is made of; an ordinary module has to define what it declares",
1642                    ),
1643            );
1644        }
1645        let (body, performed) = self.in_scope(|ck| match body_node {
1646            Some(b) => ck.block(&b.args, Some(&ret)),
1647            // A declaration has no body to check against its result type — that is what makes it a
1648            // declaration. Standing in a `unit` here and unifying it would report every line of a
1649            // `.becki` as a type error.
1650            None => Core::new(CoreKind::Const(Const::Unit), ret.as_ref().clone(), span),
1651        });
1652        if body_node.is_some() {
1653            self.unify(&body.ty, &ret, body.span, "return type");
1654        }
1655        self.locals.truncate(before);
1656        self.typarams.clear();
1657
1658        let declared = self.declared.get(&name).cloned().unwrap_or_default();
1659        // A declared effect is part of the signature whether or not the body reaches it: a stub
1660        // that will phone home later must say so today, or its callers would be re-placed by the
1661        // edit that fills the body in.
1662        let inferred = performed.union(&declared);
1663        if let Some(rv) = self.def_row.get(&name).copied() {
1664            // What the body performs *itself*, with the quantified tails taken out — they are
1665            // already in the scheme's latent row, and leaving them in `rv` as well would put the
1666            // *generic* variable into every instantiated call rather than the call's own copy of
1667            // it. Resolved first, because a tail reached through `map_list`'s own row variable is
1668            // still that tail (`docs/33` §33.2).
1669            let generic: &[RowVarId] = self
1670                .generic_rows
1671                .get(&name)
1672                .map(|v| v.as_slice())
1673                .unwrap_or(&[]);
1674            let mut own = self.subst.resolve_row(&inferred);
1675            own.tails.retain(|t| !generic.contains(t));
1676            self.subst.bind_row(rv, own);
1677        }
1678
1679        let lam = Core {
1680            kind: CoreKind::Lam {
1681                params: params.iter().map(|(id, _, _)| *id).collect(),
1682                body: Arc::new(body),
1683            },
1684            ty: Ty::Fun(param_tys, ret.clone(), latent),
1685            tier,
1686            span,
1687            last_use: false,
1688            order: crate::fields::UNORDERED,
1689            locals: 0,
1690        };
1691
1692        let mut declared_effects: Vec<Effect> = declared.atoms.iter().cloned().collect();
1693        declared_effects.sort();
1694        // An impl method's row is **inferred**, not bounded by the trait's.
1695        //
1696        // It was bounded until `docs/46` §46.5: a trait's declared row was a ceiling every impl was
1697        // held to, which meant a fallible operation could not be a trait method and `Money` could
1698        // not have `+`. A trait's row is now a floor and a piece of documentation — what a caller
1699        // of an *unknown* impl may assume — and what a caller of a known one performs is what that
1700        // impl performs. `.becki` publishes it per impl, so the boundary is not where this
1701        // becomes untrue.
1702        let row_is_declared = !declared_effects.is_empty();
1703        let bounds = self.bounds_of_def(&name);
1704        Some(Def {
1705            name,
1706            typarams: scheme.params.clone(),
1707            params,
1708            ret: *ret,
1709            body: lam,
1710            tier,
1711            effects: Vec::new(),
1712            row: inferred,
1713            declared_effects,
1714            bounds,
1715            row_is_declared,
1716            tier_is_annotated,
1717            is_declaration: body_node.is_none(),
1718            declares_signal,
1719            span,
1720            tier_span,
1721        })
1722    }
1723
1724    fn check_signal(
1725        &mut self,
1726        item: &Node,
1727        tier: Tier,
1728        tier_span: Span,
1729        tier_is_annotated: bool,
1730    ) -> Option<SignalDecl> {
1731        let target = &item.args[0];
1732        let (name_node, annot) = if target.is_form(sym::ANNOT) && target.args.len() == 2 {
1733            (&target.args[0], Some(&target.args[1]))
1734        } else {
1735            (target, None)
1736        };
1737        let name = name_node.as_var()?.name.clone();
1738        let expected = annot.map(|t| self.ty_from_node(t));
1739
1740        // A signal is a node in a graph, not a function: its row is what *evaluating its defining
1741        // expression* performs. Naming another signal contributes nothing — the dependency is an
1742        // edge, and the edge is what placement reasons about.
1743        let (expr, row) = self.in_scope(|ck| ck.expr(&item.args[1], expected.as_ref()));
1744        if let Some(e) = &expected {
1745            self.unify(&expr.ty, e, expr.span, "declared type");
1746        }
1747
1748        // The name was pre-registered so the graph could be cyclic; tie the placeholder to what
1749        // the expression actually produced.
1750        if let Some(pre) = self.schemes.get(&name).cloned() {
1751            self.unify(&expr.ty, &pre.ty, expr.span, "declared type");
1752        }
1753
1754        Some(SignalDecl {
1755            name,
1756            ty: expr.ty.clone(),
1757            expr,
1758            tier,
1759            effects: Vec::new(),
1760            row,
1761            tier_is_annotated,
1762            render: None,
1763            span: item.span(),
1764            tier_span,
1765        })
1766    }
1767
1768    // ------------------------------------------------------------------ types from syntax
1769
1770    fn ty_from_node(&mut self, n: &Node) -> Ty {
1771        if !self.enter(n.span()) {
1772            return self.subst.fresh();
1773        }
1774        let out = self.ty_from_node_inner(n);
1775        self.nesting.leave();
1776        out
1777    }
1778
1779    /// Descend one level of the tree, or refuse.
1780    ///
1781    /// `false` means the ceiling is reached: the caller returns whatever it returns for an
1782    /// expression it could not check — a fresh variable, which unifies with anything and so raises
1783    /// no second error — without recursing and without leaving.
1784    /// The same discipline for the flat axis: `false` means the block is longer than the front end
1785    /// will follow, and the caller must not recurse and must not leave.
1786    ///
1787    /// [`64`](../../../../../docs/64-compile-speed-report.md) §64.4 measured what happened without
1788    /// it — `thread 'beck-eval' has overflowed its stack`, SIGABRT, no diagnostic, at 12,000
1789    /// bindings in a debug build and 100,000 in a release one. A refusal that depends on the build
1790    /// profile is what [`adr/0007`](../../../../../docs/adr/0007-evaluator-stack-is-declared-not-discovered.md)
1791    /// established a ceiling must never be.
1792    fn enter_block(&mut self, span: Span) -> bool {
1793        if self.block_nesting.enter() {
1794            return true;
1795        }
1796        if self.block_nesting.should_report() {
1797            let note = self.block_nesting.note_about("statements in one block");
1798            self.diags.push(
1799                Diagnostic::error("B0389", "this block has too many statements to check", span)
1800                    .with_primary_label("the checker gave up here")
1801                    .with_note(note),
1802            );
1803        }
1804        false
1805    }
1806
1807    fn enter(&mut self, span: Span) -> bool {
1808        if self.nesting.enter() {
1809            return true;
1810        }
1811        if self.nesting.should_report() {
1812            let note = self.nesting.note();
1813            self.diags.push(
1814                Diagnostic::error("B0390", "the expression nests too deep to check", span)
1815                    .with_primary_label("the checker gave up here")
1816                    .with_note(note),
1817            );
1818        }
1819        false
1820    }
1821
1822    fn ty_from_node_inner(&mut self, n: &Node) -> Ty {
1823        let span = n.span();
1824        // One argument is a **nullary** function type: `() -> T`, which the parser builds with the
1825        // return type alone. It was `>= 2` here, so `() -> Int` parsed and then reported "cannot
1826        // find type `fn-type`" — and the one thing that needs it is a thunk, which is what
1827        // Felleisen's `delay` expands to (`docs/63` §63.3).
1828        if n.has_head("fn-type") && !n.args.is_empty() {
1829            let params: Vec<Ty> = n.args[..n.args.len() - 1]
1830                .iter()
1831                .map(|a| self.ty_from_node(a))
1832                .collect();
1833            let ret = self.ty_from_node(&n.args[n.args.len() - 1]);
1834            // A written function type says nothing about what the function does, so its row is a
1835            // variable: `(Todo) -> Bool` accepts a pure predicate and an effectful one alike, and
1836            // the enclosing definition inherits whichever it is handed.
1837            return Ty::fun_eff(params, ret, self.subst.fresh_row());
1838        }
1839        let Some(name) = n.head_name() else {
1840            self.error("B0308", "expected a type", span);
1841            return self.subst.fresh();
1842        };
1843        // A type parameter of the definition being read. It is rigid — `Ty::Con(name, [])` unifies
1844        // with itself and nothing else — which is what makes the body of `def first[T](xs: list[T])
1845        // -> T` provably work for every `T` rather than for whichever one the body happened to
1846        // force (`docs/32` §32.7).
1847        if self.typarams.contains(name) {
1848            if !n.args.is_empty() {
1849                self.error(
1850                    "B0313",
1851                    format!("`{name}` is a type parameter, so it takes no type arguments"),
1852                    span,
1853                );
1854            }
1855            return Ty::con(name);
1856        }
1857        // A type parameter of the *declaration* being read — positional rather than rigid, because
1858        // it has to survive into the stored `TyDecl` and be substituted for at every mention of the
1859        // declaration. See [`Checker::bind_decl_typarams`].
1860        if let Some(v) = self.decl_typarams.get(name).copied() {
1861            if !n.args.is_empty() {
1862                self.error(
1863                    "B0313",
1864                    format!("`{name}` is a type parameter, so it takes no type arguments"),
1865                    span,
1866                );
1867            }
1868            return Ty::Var(v);
1869        }
1870
1871        let args: Vec<Ty> = n.args.iter().map(|a| self.ty_from_node(a)).collect();
1872
1873        // Aliases are transparent; newtypes are not — that is what "ids of different entities must
1874        // not be interchangeable" (§3.1) means. A parameterised alias is expanded *and* applied:
1875        // `type Pairs[A] = list[Pair[A, A]]` names no type of its own, so `Pairs[Int]` has to be
1876        // `list[Pair[Int, Int]]` by the time anything else sees it.
1877        if let Some(TyDecl::Alias { ty, params, .. }) = self.types.get(name) {
1878            let (ty, params) = (ty.clone(), params.clone());
1879            if !self.check_arity(name, &params, args.len(), span) {
1880                return self.subst.fresh();
1881            }
1882            return ty::instantiate_decl(&ty, &args);
1883        }
1884
1885        let params = match prelude::builtin_arity(name) {
1886            Some(a) => letters(a),
1887            None => match self.types.get(name) {
1888                Some(d) => d.params().to_vec(),
1889                None => {
1890                    self.error("B0310", format!("cannot find type `{name}`"), span);
1891                    return self.subst.fresh();
1892                }
1893            },
1894        };
1895        if !self.check_arity(name, &params, args.len(), span) {
1896            return self.subst.fresh();
1897        }
1898        Ty::Con(Arc::from(name), args)
1899    }
1900
1901    /// A mention of a type carries exactly as many arguments as the declaration has parameters.
1902    ///
1903    /// Reported here rather than left to unification, because `Tree` with its argument missing
1904    /// would otherwise unify with `Tree[Int]` and the error would surface as a mismatch somewhere
1905    /// downstream of the line that is actually wrong.
1906    ///
1907    /// `params` is the declaration's own parameter names, so the suggestion is a program: there is
1908    /// no wildcard type in this language, and every argument is either concrete or a parameter
1909    /// bound where the mention is — including by an `impl` head, which binds its own.
1910    fn check_arity(&mut self, name: &str, params: &[Arc<str>], got: usize, span: Span) -> bool {
1911        let arity = params.len();
1912        if arity == got {
1913            return true;
1914        }
1915        let d = Diagnostic::error(
1916            "B0311",
1917            format!("`{name}` takes {arity} type argument(s), got {got}"),
1918            span,
1919        );
1920        self.diags.push(if arity == 0 {
1921            d.with_primary_label("this type takes no arguments")
1922        } else {
1923            let written = params
1924                .iter()
1925                .map(|p| p.as_ref())
1926                .collect::<Vec<_>>()
1927                .join(", ");
1928            let one = params[0].as_ref();
1929            let d = d.with_primary_label(format!("write `{name}[{written}]`"));
1930            if got < arity {
1931                // Only when something is *missing*: the reader has to get a type into the
1932                // brackets, and every way of doing that either names one or binds one.
1933                d.with_note(format!(
1934                    "each argument is a concrete type, or a parameter bound where this mention \
1935                     is — `def f[{one}]`, `model M[{one}]`, or an `impl[{one}]` head"
1936                ))
1937            } else {
1938                d
1939            }
1940        });
1941        false
1942    }
1943
1944    /// The type of two alternatives, neither of which is the other's expectation.
1945    ///
1946    /// The branches of an `if` are not actual-and-expected, and typing them as though they were is
1947    /// what refused exercise 1.43 (docs/25 §25.6 item 6): a branch whose row is closed became the
1948    /// standard a branch whose row is still a variable had to meet. [`crate::ty::Subst::unify_join`]
1949    /// is the join; this is where its failure becomes a diagnostic.
1950    fn join(&mut self, then: &Ty, alt: &Ty, span: Span) -> Ty {
1951        match self.subst.unify_join(then, alt) {
1952            Ok(ty) => ty,
1953            Err(e) => {
1954                let msg = self.mismatch(e, "the two branches");
1955                self.error("B0320", msg, span);
1956                then.clone()
1957            }
1958        }
1959    }
1960
1961    fn unify(&mut self, actual: &Ty, expected: &Ty, span: Span, what: &str) {
1962        if let Err(e) = self.subst.unify(actual, expected) {
1963            let msg = self.mismatch(e, what);
1964            self.error("B0320", msg, span);
1965        }
1966    }
1967
1968    fn mismatch(&self, e: Mismatch, what: &str) -> String {
1969        match e {
1970            Mismatch::Different(pair) => {
1971                let (a, b) = *pair;
1972                format!("{what} mismatch: expected `{b}`, found `{a}`")
1973            }
1974            Mismatch::Arity(a, b) => {
1975                format!("{what} takes {b} argument(s), got {a}")
1976            }
1977            Mismatch::Infinite => format!("{what} would be an infinite type"),
1978            Mismatch::Effects(e) => {
1979                format!("{what} may not perform {{{e}}} here")
1980            }
1981            Mismatch::UnknownEffects => {
1982                format!(
1983                    "{what} may perform effects this context does not allow: one side's effects \
1984                     are not decided here, and the other's are fixed and empty"
1985                )
1986            }
1987        }
1988    }
1989
1990    // ------------------------------------------------------------------ resolution
1991
1992    /// §2.4's hygiene rule, mechanised: a binding is a candidate for a reference exactly when its
1993    /// scope set is a subset of the reference's, and the innermost such binding wins.
1994    fn resolve(&self, s: &Symbol) -> Option<&Binding> {
1995        self.locals
1996            .iter()
1997            .rev()
1998            .chain(self.globals.iter().rev())
1999            .find(|b| b.name == s.name && b.scopes.is_subset_of(&s.scopes))
2000    }
2001
2002    // ------------------------------------------------------------------ statements
2003
2004    fn block(&mut self, stmts: &[Node], expected: Option<&Ty>) -> Core {
2005        let span = stmts.first().map(|s| s.span()).unwrap_or(Span::NONE);
2006        self.block_from(stmts, expected, span)
2007    }
2008
2009    fn block_from(&mut self, stmts: &[Node], expected: Option<&Ty>, span: Span) -> Core {
2010        let Some((first, rest)) = stmts.split_first() else {
2011            return Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span);
2012        };
2013        if !self.enter_block(first.span()) {
2014            return Core::new(CoreKind::Const(Const::Unit), self.subst.fresh(), span);
2015        }
2016        let out = self.block_step(first, rest, expected, span);
2017        self.block_nesting.leave();
2018        out
2019    }
2020
2021    /// One statement of a block, with the chain counter already entered.
2022    fn block_step(
2023        &mut self,
2024        first: &Node,
2025        rest: &[Node],
2026        expected: Option<&Ty>,
2027        span: Span,
2028    ) -> Core {
2029        if first.is_form(sym::RETURN) {
2030            if !rest.is_empty() {
2031                self.diags.push(Diagnostic::warning(
2032                    "B0330",
2033                    "statements after `return` are unreachable",
2034                    rest[0].span(),
2035                ));
2036            }
2037            return match first.args.first() {
2038                Some(e) => self.expr(e, expected),
2039                None => Core::new(CoreKind::Const(Const::Unit), Ty::unit(), first.span()),
2040            };
2041        }
2042
2043        if (first.is_form(sym::LET) || first.is_form(sym::VAR)) && first.args.len() == 2 {
2044            let target = &first.args[0];
2045            let (name_node, annot) = if target.is_form(sym::ANNOT) && target.args.len() == 2 {
2046                (&target.args[0], Some(&target.args[1]))
2047            } else {
2048                (target, None)
2049            };
2050            let want = annot.map(|t| self.ty_from_node(t));
2051            let value = self.expr(&first.args[1], want.as_ref());
2052            if let Some(w) = &want {
2053                self.unify(&value.ty, w, value.span, "declared type");
2054            }
2055            let id = self.fresh_var();
2056            if let Some(s) = name_node.as_var() {
2057                self.locals.push(Binding {
2058                    name: s.name.clone(),
2059                    scopes: s.scopes.clone(),
2060                    kind: BindKind::Local(id, value.ty.clone()),
2061                });
2062            }
2063            let body = self.block_from(rest, expected, span);
2064            self.locals.pop();
2065            let ty = body.ty.clone();
2066            return Core::new(
2067                CoreKind::Let {
2068                    var: id,
2069                    value: Box::new(value),
2070                    body: Box::new(body),
2071                },
2072                ty,
2073                first.span(),
2074            );
2075        }
2076
2077        if first.is_form(sym::FOR) || first.is_form(sym::WHILE) {
2078            self.diags.push(
2079                Diagnostic::error("B0331", "loops are not available in Phase 1", first.span())
2080                    .with_primary_label("no statement-level iteration yet")
2081                    .with_note(
2082                        "everything is an expression and `var` is not yet mutable, so a loop has \
2083                     nothing to accumulate into",
2084                    )
2085                    .with_fix("use `map_list`, `filter_list` or `fold`"),
2086            );
2087            return Core::new(CoreKind::Const(Const::Unit), Ty::unit(), first.span());
2088        }
2089
2090        // A guard clause: `if blank: return Err(…)` followed by the rest of the body. In an
2091        // expression language the rest *is* the else branch — §2.6's "everything is an expression"
2092        // is what makes early return work without a control-flow graph.
2093        if first.is_form(sym::IF) && !rest.is_empty() && first.args.len() >= 2 {
2094            let cond = self.expr(&first.args[0], Some(&Ty::bool_()));
2095            self.unify(&cond.ty, &Ty::bool_(), cond.span, "condition");
2096            let then = self.body_expr(&first.args[1], expected);
2097            let alt = match first.args.get(2) {
2098                Some(explicit) => {
2099                    self.diags.push(Diagnostic::warning(
2100                        "B0330",
2101                        "statements after an `if`/`else` that both return are unreachable",
2102                        rest[0].span(),
2103                    ));
2104                    self.body_expr(explicit, expected)
2105                }
2106                None => self.block_from(rest, expected, span),
2107            };
2108            let ty = self.join(&then.ty, &alt.ty, then.span);
2109            return Core::new(
2110                CoreKind::If {
2111                    cond: Box::new(cond),
2112                    then: Box::new(then),
2113                    alt: Box::new(alt),
2114                },
2115                ty,
2116                first.span(),
2117            );
2118        }
2119
2120        // The last statement is the block's value; anything before it is sequenced.
2121        if rest.is_empty() {
2122            return self.expr(first, expected);
2123        }
2124        let value = self.expr(first, None);
2125        let body = self.block_from(rest, expected, span);
2126        let id = self.fresh_var();
2127        let ty = body.ty.clone();
2128        Core::new(
2129            CoreKind::Let {
2130                var: id,
2131                value: Box::new(value),
2132                body: Box::new(body),
2133            },
2134            ty,
2135            first.span(),
2136        )
2137    }
2138
2139    /// A `do` block used where an expression is wanted.
2140    fn body_expr(&mut self, n: &Node, expected: Option<&Ty>) -> Core {
2141        if n.is_form(sym::DO) {
2142            let before = self.locals.len();
2143            let out = self.block(&n.args, expected);
2144            self.locals.truncate(before);
2145            out
2146        } else {
2147            self.expr(n, expected)
2148        }
2149    }
2150
2151    // ------------------------------------------------------------------ expressions
2152
2153    fn expr(&mut self, n: &Node, expected: Option<&Ty>) -> Core {
2154        if !self.enter(n.span()) {
2155            return Core::new(CoreKind::Const(Const::Unit), self.subst.fresh(), n.span());
2156        }
2157        let out = self.expr_inner(n, expected);
2158        self.nesting.leave();
2159        out
2160    }
2161
2162    fn expr_inner(&mut self, n: &Node, expected: Option<&Ty>) -> Core {
2163        let span = n.span();
2164
2165        // `|` and `@` mean one thing each and both are patterns. They are in the expression
2166        // grammar because §2.6's patterns *are* expressions, so this is where they are refused —
2167        // the same division `*rest` has had since `docs/33`.
2168        if n.args.len() == 2 && (n.has_head("|") || n.has_head("@")) {
2169            let op = if n.has_head("|") { "|" } else { "@" };
2170            self.error(
2171                "B0357",
2172                format!("`{op}` is only meaningful in a `case` pattern"),
2173                span,
2174            );
2175            return Core::new(CoreKind::Const(Const::Unit), self.subst.fresh(), span);
2176        }
2177
2178        if let Some(l) = n.as_lit() {
2179            return match l {
2180                Lit::Int(i) => Core::new(CoreKind::Const(Const::Int(*i)), Ty::int(), span),
2181                Lit::Float(f) => {
2182                    Core::new(CoreKind::Const(Const::Float(*f)), Ty::con(Ty::FLOAT), span)
2183                }
2184                Lit::Bool(b) => Core::new(CoreKind::Const(Const::Bool(*b)), Ty::bool_(), span),
2185                Lit::Str(s) => Core::new(CoreKind::Const(Const::Str(s.clone())), Ty::str_(), span),
2186                Lit::Keyword(k) => {
2187                    Core::new(CoreKind::Const(Const::Str(k.clone())), Ty::str_(), span)
2188                }
2189            };
2190        }
2191
2192        if let Some(s) = n.as_var() {
2193            if s.as_str() == "unit" {
2194                return Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span);
2195            }
2196            return self.var_ref(s, span);
2197        }
2198
2199        let head = n.head_name().unwrap_or("");
2200        match head {
2201            sym::DO => self.body_expr(n, expected),
2202            sym::IF if n.args.len() >= 2 => {
2203                let cond = self.expr(&n.args[0], Some(&Ty::bool_()));
2204                self.unify(&cond.ty, &Ty::bool_(), cond.span, "condition");
2205                let then = self.body_expr(&n.args[1], expected);
2206                let alt = match n.args.get(2) {
2207                    Some(a) => self.body_expr(a, expected),
2208                    None => Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span),
2209                };
2210                let ty = self.join(&then.ty, &alt.ty, alt.span);
2211                Core::new(
2212                    CoreKind::If {
2213                        cond: Box::new(cond),
2214                        then: Box::new(then),
2215                        alt: Box::new(alt),
2216                    },
2217                    ty,
2218                    span,
2219                )
2220            }
2221            sym::FN if n.args.len() == 2 => self.lambda(n, expected, span),
2222            sym::RAISE if n.args.len() == 1 => self.raise_expr(&n.args[0], span),
2223            sym::TRY if n.args.len() == 1 => self.try_expr(&n.args[0], expected, span),
2224            sym::PARALLEL if n.args.len() == 1 => self.parallel_expr(&n.args[0], expected, span),
2225            sym::MATCH if !n.args.is_empty() => self.match_expr(n, expected, span),
2226            sym::LIST => {
2227                let elem = expected
2228                    .and_then(|t| match t {
2229                        Ty::Con(c, args) if c.as_ref() == Ty::LIST && args.len() == 1 => {
2230                            Some(args[0].clone())
2231                        }
2232                        _ => None,
2233                    })
2234                    .unwrap_or_else(|| self.subst.fresh());
2235                let items: Vec<Core> = n
2236                    .args
2237                    .iter()
2238                    .map(|a| {
2239                        let c = self.expr(a, Some(&elem));
2240                        self.unify(&c.ty, &elem, c.span, "list element");
2241                        c
2242                    })
2243                    .collect();
2244                Core::new(CoreKind::ListLit(items), Ty::list(elem), span)
2245            }
2246            sym::MAP => {
2247                let k = self.subst.fresh();
2248                let v = self.subst.fresh();
2249                let mut pairs = Vec::new();
2250                for pair in n.args.chunks(2) {
2251                    if pair.len() != 2 {
2252                        break;
2253                    }
2254                    let kc = self.expr(&pair[0], Some(&k));
2255                    self.unify(&kc.ty, &k, kc.span, "map key");
2256                    let vc = self.expr(&pair[1], Some(&v));
2257                    self.unify(&vc.ty, &v, vc.span, "map value");
2258                    pairs.push((kc, vc));
2259                }
2260                Core::new(CoreKind::MapLit(pairs), Ty::map(k, v), span)
2261            }
2262            sym::RECORD => self.record_lit(n, expected, span),
2263            sym::DOT if n.args.len() >= 2 => self.dot(n, span),
2264            "index" if n.args.len() == 2 => {
2265                let base = self.expr(&n.args[0], None);
2266                let key = self.expr(&n.args[1], None);
2267                let v = self.subst.fresh();
2268                self.unify(
2269                    &base.ty,
2270                    &Ty::map(key.ty.clone(), v.clone()),
2271                    span,
2272                    "indexing",
2273                );
2274                Core::new(
2275                    CoreKind::Prim {
2276                        op: Prim::MapGet,
2277                        args: vec![base, key],
2278                    },
2279                    Ty::option(v),
2280                    span,
2281                )
2282            }
2283            "+" | "-" | "*" | "/" if n.args.len() == 2 => {
2284                let op = match head {
2285                    "+" => Prim::Add,
2286                    "-" => Prim::Sub,
2287                    "*" => Prim::Mul,
2288                    _ => Prim::Div,
2289                };
2290                self.arith(op, &n.args[0], &n.args[1], expected, span)
2291            }
2292            "negate" if n.args.len() == 1 => {
2293                let arg = self.expr(&n.args[0], expected);
2294                let want = self.numeric_of(&arg.ty, expected).unwrap_or_else(Ty::int);
2295                self.unify(&arg.ty, &want, arg.span, "operand of `-`");
2296                Core::new(
2297                    CoreKind::Prim {
2298                        op: Prim::Neg,
2299                        args: vec![arg],
2300                    },
2301                    want,
2302                    span,
2303                )
2304            }
2305            "abs" if n.args.len() == 1 && n.applied => {
2306                // The one *named* member of the tower that is resolved rather than declared. SICP
2307                // writes `abs` at both tiers, and a scheme cannot say "Int or Float" without a
2308                // numeric class; `docs/32` §32.3 argues why the class is not worth it yet.
2309                let arg = self.expr(&n.args[0], expected);
2310                let want = match self.numeric_of(&arg.ty, expected) {
2311                    Some(t) => t,
2312                    None => Ty::int(),
2313                };
2314                self.unify(&arg.ty, &want, arg.span, "operand of `abs`");
2315                Core::new(
2316                    CoreKind::Prim {
2317                        op: Prim::Abs,
2318                        args: vec![arg],
2319                    },
2320                    want,
2321                    span,
2322                )
2323            }
2324            sym::QUOTE => {
2325                self.error("B0332", "a `quote` survived macro expansion", span);
2326                Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span)
2327            }
2328            sym::KW_ARG => {
2329                self.error("B0333", "a keyword argument outside a call", span);
2330                Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span)
2331            }
2332            _ if n.applied => self.call(n, expected, span),
2333            _ => {
2334                self.error("B0334", "unsupported expression", span);
2335                Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span)
2336            }
2337        }
2338    }
2339
2340    /// The arithmetic operators, resolved from their operands rather than from a type class.
2341    ///
2342    /// Phase 1 gave `+` this treatment already, for `Str` concatenation: "a `(a, a) -> a` scheme
2343    /// would let `Bool + Bool` typecheck". The numeric tower needs the same answer for the same
2344    /// reason and one more tier — a real — and `docs/32` §32.3 sets out why an ad-hoc resolution is
2345    /// the honest thing to build before traits exist rather than a stand-in for them.
2346    ///
2347    /// The rule is: whichever of the two operands and the expectation *first* resolves to a numeric
2348    /// type decides, and `Int` is what an expression with nothing known about it defaults to — so
2349    /// every program written before reals existed still means what it meant.
2350    fn arith(
2351        &mut self,
2352        op: Prim,
2353        lhs_node: &Node,
2354        rhs_node: &Node,
2355        expected: Option<&Ty>,
2356        span: Span,
2357    ) -> Core {
2358        let lhs = self.expr(lhs_node, None);
2359        let rhs = self.expr(rhs_node, None);
2360        // `+` alone also concatenates, which is what the sketch's footer wants.
2361        let is_str = op == Prim::Add
2362            && (self.subst.resolve(&lhs.ty).con_name() == Some(Ty::STR)
2363                || self.subst.resolve(&rhs.ty).con_name() == Some(Ty::STR)
2364                || expected
2365                    .map(|t| t.con_name() == Some(Ty::STR))
2366                    .unwrap_or(false));
2367        let numeric = if is_str {
2368            Some(Ty::str_())
2369        } else {
2370            self.numeric_of(&lhs.ty, None)
2371                .or_else(|| self.numeric_of(&rhs.ty, None))
2372                .or_else(|| expected.and_then(|t| self.numeric_of(t, None)))
2373        };
2374        // Neither operand is a number and neither is a string: the third floor of the tower, which
2375        // a user's type joins by implementing `Num` (`docs/41` §41.2). Only when there is an
2376        // implementation to dispatch to — otherwise the old rule runs and says what it always said,
2377        // so `1 + true` is still a mismatch rather than a lecture about traits.
2378        if numeric.is_none() {
2379            if let Some(core) = self.arith_through_num(op, &lhs, &rhs, span) {
2380                return core;
2381            }
2382        }
2383        let want = numeric.unwrap_or_else(Ty::int);
2384        let label = format!("operand of `{}`", op.name());
2385        self.unify(&lhs.ty, &want, lhs.span, &label);
2386        self.unify(&rhs.ty, &want, rhs.span, &label);
2387        Core::new(
2388            CoreKind::Prim {
2389                op,
2390                args: vec![lhs, rhs],
2391            },
2392            want,
2393            span,
2394        )
2395    }
2396
2397    /// `a + b` where `a` is neither a number nor a string, resolved through `Num`.
2398    ///
2399    /// SICP §2.5.1's generic arithmetic, and `docs/32` §32.3's deferred decision taken: the four
2400    /// operators are the four methods of one prelude trait, so a `Rational` joins the tower the way
2401    /// the book joins it — by implementing the operations, not by being added to a list inside the
2402    /// compiler.
2403    ///
2404    /// Returns `None` when there is nothing to dispatch to, and the caller falls back to the
2405    /// numeric rule unchanged. The failure this *does* report is the one worth reporting: an
2406    /// operand whose type is a declared one with no implementation, where "expected `Int`, found
2407    /// `Rational`" names the symptom and `impl Num for Rational` is the cure.
2408    fn arith_through_num(&mut self, op: Prim, lhs: &Core, rhs: &Core, span: Span) -> Option<Core> {
2409        let method: Arc<str> = Arc::from(prelude::num_method(op)?);
2410        let num: Arc<str> = Arc::from(prelude::NUM);
2411        let ty = [&lhs.ty, &rhs.ty]
2412            .into_iter()
2413            .map(|t| self.subst.resolve(t))
2414            .find(|t| self.joins_the_tower(t))?;
2415        let head = ty.con_name().map(Arc::<str>::from)?;
2416        let known = self.impls.contains_key(&(num.clone(), head.clone()))
2417            || self
2418                .resolve(&Symbol::new(traits::mangle(&num, &method, &head)))
2419                .is_some();
2420        if !known {
2421            // A declared type with no implementation. Reported here rather than left to the numeric
2422            // rule, because "this type is not in the tower, and here is how to put it there" is a
2423            // different sentence from "this is not an `Int`".
2424            if self.types.contains_key(&head) {
2425                self.diags.push(
2426                    Diagnostic::error(
2427                        "B0387",
2428                        format!("`{head}` does not implement `{num}`"),
2429                        span,
2430                    )
2431                    .with_primary_label(format!("`{}` resolves through it", op.name()))
2432                    .with_fix(format!("write `impl {num} for {head}`")),
2433                );
2434                return Some(Core::new(CoreKind::Const(Const::Unit), ty, span));
2435            }
2436            return None;
2437        }
2438        let func = self.dictionary(&num, &method, &ty, span)?;
2439        let Ty::Fun(params, ret, row) = self.subst.resolve(&func.ty) else {
2440            return None;
2441        };
2442        self.perform(&row);
2443        let label = format!("operand of `{}`", op.name());
2444        self.unify(&lhs.ty, &params[0], lhs.span, &label);
2445        self.unify(&rhs.ty, &params[1], rhs.span, &label);
2446        Some(Core::new(
2447            CoreKind::App {
2448                func: Box::new(func),
2449                args: vec![lhs.clone(), rhs.clone()],
2450            },
2451            *ret,
2452            span,
2453        ))
2454    }
2455
2456    /// Could this type be a floor of the numeric tower a user built?
2457    ///
2458    /// Everything with a name except the two the primitives already handle and the one `+` also
2459    /// concatenates. A unification variable is not: an expression nothing has pinned down yet still
2460    /// defaults to `Int`, which is what keeps every program written before this compiling.
2461    fn joins_the_tower(&self, t: &Ty) -> bool {
2462        !matches!(
2463            t.con_name(),
2464            None | Some(Ty::INT) | Some(Ty::FLOAT) | Some(Ty::STR)
2465        )
2466    }
2467
2468    /// `Int` or `Float` if either is what this type already is, otherwise nothing.
2469    ///
2470    /// "Otherwise nothing" rather than "otherwise Int" matters: an unresolved variable must not
2471    /// commit the expression, or `abs(x)` inside a `Float -> Float` definition would fix `x` to
2472    /// `Int` before the parameter's annotation had been consulted.
2473    fn numeric_of(&mut self, ty: &Ty, expected: Option<&Ty>) -> Option<Ty> {
2474        for candidate in [Some(ty), expected].into_iter().flatten() {
2475            match self.subst.resolve(candidate).con_name() {
2476                Some(Ty::INT) => return Some(Ty::int()),
2477                Some(Ty::FLOAT) => return Some(Ty::con(Ty::FLOAT)),
2478                _ => {}
2479            }
2480        }
2481        None
2482    }
2483
2484    fn var_ref(&mut self, s: &Symbol, span: Span) -> Core {
2485        let Some(b) = self.resolve(s).cloned() else {
2486            // A sibling in the same `parallel:` scope is absent for a reason, so say the reason.
2487            if self.parallel_siblings.contains(&s.name) {
2488                self.error(
2489                    "B0398",
2490                    format!(
2491                        "`{s}` is another child of this `parallel:` scope, so it has not run yet — \
2492                         children cannot see each other, which is what lets them run together"
2493                    ),
2494                    span,
2495                );
2496            } else {
2497                self.error("B0340", format!("cannot find `{s}` in this scope"), span);
2498            }
2499            let t = self.subst.fresh();
2500            return Core::new(CoreKind::Const(Const::Unit), t, span);
2501        };
2502        match b.kind {
2503            BindKind::Local(id, ty) => Core::new(CoreKind::Var(id), ty, span),
2504            // A trait method is resolved from the type of its receiver, so there is nothing to
2505            // hand over until it is applied. `map_list(xs, show)` would need a dictionary.
2506            BindKind::TraitMethod(m) => {
2507                let owner = self.trait_methods.get(&m).cloned();
2508                self.diags.push(
2509                    Diagnostic::error(
2510                        "B0386",
2511                        format!("`{m}` is a trait method and cannot be used as a value"),
2512                        span,
2513                    )
2514                    .with_primary_label(match &owner {
2515                        Some(t) => format!("declared by trait `{t}`"),
2516                        None => "a trait method".into(),
2517                    })
2518                    .with_note(
2519                        "which implementation it means is decided by the type of its receiver, so \
2520                         it has to be called rather than passed; passing one needs bounds on a type \
2521                         parameter, which is not built",
2522                    ),
2523                );
2524                Core::new(CoreKind::Const(Const::Unit), self.subst.fresh(), span)
2525            }
2526            BindKind::Global(name) => {
2527                if self.dicts.contains_key(&name) {
2528                    self.diags.push(
2529                        Diagnostic::error(
2530                            "B0386",
2531                            format!("`{name}` has a bound, so it cannot be used as a value"),
2532                            span,
2533                        )
2534                        .with_note(
2535                            "a bounded definition is handed its implementations at the call site, \
2536                             and a reference that is never called has no call site to hand them \
2537                             over",
2538                        ),
2539                    );
2540                }
2541                let ty = self
2542                    .schemes
2543                    .get(&name)
2544                    .map(|sc| self.subst.instantiate(sc))
2545                    .unwrap_or_else(|| self.subst.fresh());
2546                Core::new(CoreKind::Global(name), ty, span)
2547            }
2548            BindKind::Prim(p) => {
2549                // A primitive used as a value becomes a lambda wrapping it, so it can be passed to
2550                // `map_list` like any other function.
2551                let (_, scheme) = self.prims.get(p.name()).cloned().expect("prim registered");
2552                let ty = self.subst.instantiate(&scheme);
2553                // Referencing a function performs nothing; the row rides on the *type* and is
2554                // charged to whoever applies it.
2555                let Ty::Fun(params, ret, latent) = ty.clone() else {
2556                    return Core::new(
2557                        CoreKind::Prim {
2558                            op: p,
2559                            args: vec![],
2560                        },
2561                        ty,
2562                        span,
2563                    );
2564                };
2565                let ids: Vec<VarId> = params.iter().map(|_| self.fresh_var()).collect();
2566                let args: Vec<Core> = ids
2567                    .iter()
2568                    .zip(&params)
2569                    .map(|(id, t)| Core::new(CoreKind::Var(*id), t.clone(), span))
2570                    .collect();
2571                Core::new(
2572                    CoreKind::Lam {
2573                        params: ids.into(),
2574                        body: Arc::new(Core::new(
2575                            CoreKind::Prim { op: p, args },
2576                            *ret.clone(),
2577                            span,
2578                        )),
2579                    },
2580                    Ty::Fun(params, ret, latent),
2581                    span,
2582                )
2583            }
2584            BindKind::Ctor(union, variant) => self.make(&union, Some(&variant), &[], span),
2585            BindKind::Model(model) => self.make(&model, None, &[], span),
2586        }
2587    }
2588
2589    // ---------------------------------------------------------- failure, as a row label
2590
2591    /// `raise e` — perform `raises(T)`, and have no type of its own.
2592    ///
2593    /// The result is a fresh variable rather than `never`, for the reason `docs/38` §38.4 gives for
2594    /// the whole shape: a raise is an *effect*, so the expression it stands in for is whatever the
2595    /// context wanted. `if text == "": raise Blank else: text` is a `Str`.
2596    fn raise_expr(&mut self, arg: &Node, span: Span) -> Core {
2597        let value = self.expr(arg, None);
2598        let ty = self.subst.resolve(&value.ty);
2599        let Some(name) = error_ty_name(&ty) else {
2600            self.error(
2601                "B0391",
2602                format!("a raised value must have a declared type, and this one is `{ty}`"),
2603                value.span,
2604            );
2605            return Core::new(CoreKind::Const(Const::Unit), self.subst.fresh(), span);
2606        };
2607        // The atom names the type, so a handler can say what it catches. This is why `Raise` is the
2608        // one primitive whose row `Prim::effects` cannot state: it is a function of the argument.
2609        self.perform(&Row::of([Effect::Raises(name)]));
2610        Core::new(
2611            CoreKind::Prim {
2612                op: Prim::Raise,
2613                args: vec![value],
2614            },
2615            self.subst.fresh(),
2616            span,
2617        )
2618    }
2619
2620    /// `try: block` — run the block, and reify one failure as a `Result[T, E]`.
2621    ///
2622    /// This is the handler, and it is a *form*: lexically scoped by construction, with no dynamic
2623    /// search for who handles what (POPL 2019's result, `docs/38` §38.4).
2624    ///
2625    /// **It catches one error type and lets every other failure travel**, which is what makes it
2626    /// composable rather than a barrier. `E` comes from the expectation where there is one — a
2627    /// `try:` almost always flows into something whose type says `Result[T, E]` — and from the
2628    /// block's own row where there is not. Taking it from the expectation is not a convenience: a
2629    /// row is decided lazily, so a call to a definition declared *later* in the file contributes a
2630    /// row *variable* at this point and a handler that could only read atoms would be wrong about
2631    /// exactly the forward references a program is made of.
2632    ///
2633    /// Whatever is not caught stays in the enclosing row — other `raises` atoms, every other
2634    /// effect, and the row variables, which may hide a failure this handler has no type for. That
2635    /// last point is why the primitive is given the name of what it catches: the runtime compares.
2636    fn try_expr(&mut self, body: &Node, expected: Option<&Ty>, span: Span) -> Core {
2637        // The `Result[T, E]` a caller expects tells the block both halves: what its value type
2638        // should be, and which failure this handler is for.
2639        let (inner_expected, expected_error) = match expected.map(|t| self.subst.resolve(t)) {
2640            Some(Ty::Con(c, args)) if c.as_ref() == Ty::RESULT && args.len() == 2 => (
2641                Some(args[0].clone()),
2642                match self.subst.resolve(&args[1]) {
2643                    Ty::Con(e, es) if es.is_empty() => Some(e),
2644                    _ => None,
2645                },
2646            ),
2647            _ => (None, None),
2648        };
2649
2650        let outer = std::mem::take(&mut self.row);
2651        let before = self.locals.len();
2652        let core = self.body_expr(body, inner_expected.as_ref());
2653        self.locals.truncate(before);
2654        // Resolved, not raw: a call to something whose row is still a variable contributes a tail,
2655        // and the atoms behind it are only visible once the substitution has caught up.
2656        let inner = self
2657            .subst
2658            .resolve_row(&std::mem::replace(&mut self.row, outer));
2659
2660        let mut raised: Vec<Arc<str>> = Vec::new();
2661        for atom in &inner.atoms {
2662            if let Effect::Raises(t) = atom {
2663                if !raised.contains(t) {
2664                    raised.push(t.clone());
2665                }
2666            }
2667        }
2668        raised.sort();
2669
2670        let error = match expected_error {
2671            Some(e) => e,
2672            None => match raised.len() {
2673                1 => raised[0].clone(),
2674                0 => {
2675                    self.error(
2676                        "B0392",
2677                        "nothing here can fail, and nothing says what this would catch",
2678                        span,
2679                    );
2680                    return core;
2681                }
2682                _ => {
2683                    let names: Vec<String> = raised.iter().map(|t| format!("`{t}`")).collect();
2684                    self.error(
2685                        "B0393",
2686                        format!(
2687                            "this block can fail in {} ways ({}), so say which one to catch — a \
2688                             `Result[T, E]` on the enclosing signature is how",
2689                            raised.len(),
2690                            names.join(", ")
2691                        ),
2692                        span,
2693                    );
2694                    raised[0].clone()
2695                }
2696            },
2697        };
2698
2699        // Everything except the failure being caught is still performed by the enclosing
2700        // definition. A handler catches one failure; it does not launder a `durable`, and it does
2701        // not silently swallow a second error type.
2702        let mut rest = Row::empty();
2703        rest.tails = inner.tails.clone();
2704        for atom in &inner.atoms {
2705            if !matches!(atom, Effect::Raises(t) if *t == error) {
2706                rest.atoms.insert(atom.clone());
2707            }
2708        }
2709        self.perform(&rest);
2710
2711        let value_ty = core.ty.clone();
2712        let result_ty = Ty::app(Ty::RESULT, vec![value_ty, Ty::con(&error)]);
2713        let thunk = Core::new(
2714            CoreKind::Lam {
2715                params: Arc::from(Vec::new()),
2716                body: Arc::new(core),
2717            },
2718            self.subst.fresh(),
2719            span,
2720        );
2721        Core::new(
2722            CoreKind::Prim {
2723                op: Prim::Try,
2724                args: vec![
2725                    thunk,
2726                    Core::new(CoreKind::Const(Const::Str(error.clone())), Ty::str_(), span),
2727                ],
2728            },
2729            result_ty,
2730            span,
2731        )
2732    }
2733
2734    /// `parallel:` — a scope whose bindings are its children.
2735    ///
2736    /// Two rules make the scope's answer independent of the order its children ran in, which is
2737    /// what lets a backend run them together:
2738    ///
2739    /// * **no child can name another.** Each initialiser is checked before any of the names is
2740    ///   bound, so a reference to a sibling does not resolve; `parallel_siblings` is what turns
2741    ///   that into `B0398` rather than into "cannot find".
2742    /// * **no child performs an effect another child could observe.** [`observable_order`] is the
2743    ///   list, and it is about state *inside* the program or its own substrate. `net.out` is not
2744    ///   on it deliberately: a remote host's state is not Beck's to order, and two outbound calls
2745    ///   are the case this form exists for.
2746    ///
2747    /// What the scope performs is `spawn`, every child's row, and the tail's — so a child that can
2748    /// fail makes the scope fallible, and a `try:` outside it catches at the scope. That is
2749    /// [`docs/38`](../../../../../docs/38-literature-survey.md) §38.4's "cancellation is the error
2750    /// row crossing the scope", with the ordered join deciding *which* failure when more than one
2751    /// child could raise: the earliest child in the order they are written, rather than the
2752    /// earliest to finish.
2753    fn parallel_expr(&mut self, body: &Node, expected: Option<&Ty>, span: Span) -> Core {
2754        let stmts: &[Node] = if body.is_form(sym::DO) {
2755            &body.args
2756        } else {
2757            std::slice::from_ref(body)
2758        };
2759
2760        // The children are the leading bindings; everything from the first non-binding statement
2761        // on is the tail, which runs after the join with all of them in scope.
2762        let children = stmts
2763            .iter()
2764            .take_while(|s| (s.is_form(sym::LET) || s.is_form(sym::VAR)) && s.args.len() == 2)
2765            .count();
2766        if children < 2 {
2767            self.error(
2768                "B0397",
2769                format!(
2770                    "a `parallel:` scope runs its bindings as children, and this one has {children}"
2771                ),
2772                span,
2773            );
2774            let before = self.locals.len();
2775            let out = self.block_from(stmts, expected, span);
2776            self.locals.truncate(before);
2777            return out;
2778        }
2779
2780        // The scope starts concurrent work whatever its children do, so the atom is charged here
2781        // rather than left to [`Prim::effects`] — nothing walks a prim's table on the way to a row,
2782        // and a `parallel:` over two pure children still has to place on a tier that can run one.
2783        self.perform(&Row::of([Effect::Spawn]));
2784
2785        let outer_siblings = std::mem::take(&mut self.parallel_siblings);
2786        let before = self.locals.len();
2787        let mut thunks = Vec::with_capacity(children);
2788        let mut bound: Vec<(VarId, Ty)> = Vec::with_capacity(children);
2789        let mut names: Vec<(Option<Symbol>, Ty)> = Vec::with_capacity(children);
2790
2791        for stmt in &stmts[..children] {
2792            let target = &stmt.args[0];
2793            let (name_node, annot) = if target.is_form(sym::ANNOT) && target.args.len() == 2 {
2794                (&target.args[0], Some(&target.args[1]))
2795            } else {
2796                (target, None)
2797            };
2798            let want = annot.map(|t| self.ty_from_node(t));
2799            // A child's effects belong to the child, so they are collected here and charged to the
2800            // scope below — the same separation a lambda body gets, and for the same reason.
2801            let (value, row) = self.in_scope(|ck| ck.expr(&stmt.args[1], want.as_ref()));
2802            if let Some(w) = &want {
2803                self.unify(&value.ty, w, value.span, "declared type");
2804            }
2805            let row = self.subst.resolve_row(&row);
2806            let refused: Vec<String> = row
2807                .atoms
2808                .iter()
2809                .filter(|a| observable_order(a))
2810                .map(|a| format!("`{}`", a.name()))
2811                .collect();
2812            if !refused.is_empty() {
2813                self.error(
2814                    "B0399",
2815                    format!(
2816                        "a child of a `parallel:` scope may not perform {} — another child would \
2817                         be able to tell what order they ran in",
2818                        refused.join(", ")
2819                    ),
2820                    value.span,
2821                );
2822            }
2823            self.perform(&row);
2824
2825            let ty = value.ty.clone();
2826            let vspan = value.span;
2827            thunks.push(Core::new(
2828                CoreKind::Lam {
2829                    params: Arc::from(Vec::new()),
2830                    body: Arc::new(value),
2831                },
2832                Ty::fun_eff(Vec::new(), ty.clone(), row),
2833                vspan,
2834            ));
2835            let id = self.fresh_var();
2836            bound.push((id, ty.clone()));
2837            names.push((name_node.as_var().cloned(), ty));
2838            if let Some(s) = name_node.as_var() {
2839                self.parallel_siblings.push(s.name.clone());
2840            }
2841        }
2842
2843        // Only now do the children's names exist, which is what the first rule means.
2844        self.parallel_siblings = outer_siblings;
2845        for ((id, _), (name, ty)) in bound.iter().zip(names.iter()) {
2846            if let Some(s) = name {
2847                self.locals.push(Binding {
2848                    name: s.name.clone(),
2849                    scopes: s.scopes.clone(),
2850                    kind: BindKind::Local(*id, ty.clone()),
2851                });
2852            }
2853        }
2854        let tail = self.block_from(&stmts[children..], expected, span);
2855        self.locals.truncate(before);
2856
2857        let tail_ty = tail.ty.clone();
2858        let param_tys: Vec<Ty> = bound.iter().map(|(_, t)| t.clone()).collect();
2859        let ids: Vec<VarId> = bound.iter().map(|(id, _)| *id).collect();
2860        let k = Core::new(
2861            CoreKind::Lam {
2862                params: ids.into(),
2863                body: Arc::new(tail),
2864            },
2865            // The continuation's own row is empty: whatever the tail performs has already been
2866            // charged to the enclosing definition by `block_from`, and charging it twice would put
2867            // it in the scope's row a second time.
2868            Ty::fun_eff(param_tys, tail_ty.clone(), Row::empty()),
2869            span,
2870        );
2871        let mut args = thunks;
2872        args.push(k);
2873        Core::new(
2874            CoreKind::Prim {
2875                op: Prim::Parallel,
2876                args,
2877            },
2878            tail_ty,
2879            span,
2880        )
2881    }
2882
2883    fn lambda(&mut self, n: &Node, expected: Option<&Ty>, span: Span) -> Core {
2884        let want: Option<(Vec<Ty>, Ty)> = expected.and_then(|t| match self.subst.resolve(t) {
2885            Ty::Fun(ps, r, _) => Some((ps, *r)),
2886            _ => None,
2887        });
2888        let before = self.locals.len();
2889        let mut ids = Vec::new();
2890        let mut tys = Vec::new();
2891        for (i, p) in n.args[0].args.iter().enumerate() {
2892            let (target, annot) = if p.is_form(sym::ANNOT) && p.args.len() == 2 {
2893                (&p.args[0], Some(&p.args[1]))
2894            } else {
2895                (p, None)
2896            };
2897            let ty = match annot {
2898                Some(t) => self.ty_from_node(t),
2899                None => want
2900                    .as_ref()
2901                    .and_then(|(ps, _)| ps.get(i).cloned())
2902                    .unwrap_or_else(|| self.subst.fresh()),
2903            };
2904            let id = self.fresh_var();
2905            if let Some(s) = target.as_var() {
2906                self.locals.push(Binding {
2907                    name: s.name.clone(),
2908                    scopes: s.scopes.clone(),
2909                    kind: BindKind::Local(id, ty.clone()),
2910                });
2911            }
2912            ids.push(id);
2913            tys.push(ty);
2914        }
2915        let ret_want = want.as_ref().map(|(_, r)| r.clone());
2916        // What a lambda's body does is what the *lambda* does when called, not what the enclosing
2917        // definition does by writing it down. `sort_by(xs, lambda t: t.text)` performs nothing.
2918        let (body, row) = self.in_scope(|ck| ck.body_expr(&n.args[1], ret_want.as_ref()));
2919        self.locals.truncate(before);
2920        let ret = body.ty.clone();
2921        Core::new(
2922            CoreKind::Lam {
2923                params: ids.into(),
2924                body: Arc::new(body),
2925            },
2926            Ty::fun_eff(tys, ret, row),
2927            span,
2928        )
2929    }
2930
2931    fn match_expr(&mut self, n: &Node, expected: Option<&Ty>, span: Span) -> Core {
2932        let scrutinee = self.expr(&n.args[0], None);
2933        let scrut_ty = self.subst.resolve(&scrutinee.ty);
2934        let result = expected.cloned().unwrap_or_else(|| self.subst.fresh());
2935
2936        let mut arms = Vec::new();
2937        for arm in &n.args[1..] {
2938            // Two arguments is `case p:`; three is `case p if g:`, with the guard last so that
2939            // everything reading `args[1]` for the body still reads the body.
2940            if !arm.is_form(sym::CASE) || !(2..=3).contains(&arm.args.len()) {
2941                continue;
2942            }
2943            let before = self.locals.len();
2944            let pattern = self.pattern(&arm.args[0], &scrut_ty);
2945            // In the scope of what the pattern bound, which is the whole reason a guard is not an
2946            // `if` around the `match`.
2947            let guard = arm.args.get(2).map(|g| {
2948                let c = self.expr(g, Some(&Ty::bool_()));
2949                self.unify(&c.ty, &Ty::bool_(), c.span, "a `case` guard");
2950                c
2951            });
2952            let body = self.body_expr(&arm.args[1], Some(&result));
2953            self.unify(&body.ty, &result, body.span, "match arm");
2954            self.locals.truncate(before);
2955            arms.push(Arm {
2956                pattern,
2957                guard,
2958                body,
2959                span: arm.span(),
2960            });
2961        }
2962
2963        // §3.1: "a fold over a `union Event` that misses a case is a compile error — this single
2964        // check carries the migration story" (§3.9). What counts as covered is not a set of names
2965        // any more, because `case Some(Added(id))` names `Some` and covers a fraction of it —
2966        // [`exhaust`] is the check, and it answers for a list and a union with one algorithm.
2967        // A guarded arm contributes **nothing** to coverage: whether it matches depends on a value
2968        // rather than on a shape, so a checker that counted it would call a `match` exhaustive on
2969        // the strength of a condition that can be false. `case _ if ok(x):` is not a `case _`.
2970        let shapes: Vec<Pattern> = arms
2971            .iter()
2972            .filter(|a| a.guard.is_none())
2973            .map(|a| a.pattern.clone())
2974            .collect();
2975        // The scrutinee's type has to be *resolved* here rather than where it was read: an arm's
2976        // body can be what decides it, so asking before the arms are checked asks a variable.
2977        let scrut_ty = self.subst.resolve(&scrut_ty);
2978        // An arm no value can reach is dead code, and nested patterns are what make it easy to
2979        // write by accident: `case Some(Circle(r))`, then `case Some(_)`, then `case Some(Square)`.
2980        // A warning rather than an error — a `case _` after every variant is a habit, and refusing
2981        // a habit changes what compiles rather than telling somebody something.
2982        // Judged against the *unguarded* arms above each one, for the same reason: an arm above
2983        // that only sometimes matches cannot make this one dead. `guarded` maps a position in
2984        // `shapes` back to the arm it came from.
2985        let guarded: Vec<usize> = arms
2986            .iter()
2987            .enumerate()
2988            .filter(|(_, a)| a.guard.is_none())
2989            .map(|(i, _)| i)
2990            .collect();
2991        for i in exhaust::unreachable(&shapes, &scrut_ty, &self.types)
2992            .into_iter()
2993            .map(|i| guarded[i])
2994        {
2995            self.diags.push(
2996                Diagnostic::warning("B0355", "this case can never match", arms[i].span)
2997                    .with_primary_label("the arms above it already cover every value this matches")
2998                    .with_note(
2999                        "an arm that cannot run is either a mistake about what the arms above it \
3000                         match, or a line to delete",
3001                    ),
3002            );
3003        }
3004
3005        if let exhaust::Coverage::Missing(missing) =
3006            exhaust::coverage(&shapes, &scrut_ty, &self.types)
3007        {
3008            let note = if scrut_ty.con_name() == Some(Ty::LIST) {
3009                "a list is empty or it is not, and a fold that handles only one of those is a \
3010                 fold that fails on the input nobody tested"
3011            } else {
3012                "adding a variant must break every fold that consumes it — that is what makes a \
3013                 missed migration a compile error rather than a 3 a.m. page"
3014            };
3015            self.diags.push(
3016                Diagnostic::error("B0341", "match is not exhaustive", span)
3017                    .with_primary_label(format!("missing: {}", missing.join(", ")))
3018                    .with_note(note),
3019            );
3020        }
3021
3022        Core::new(
3023            CoreKind::Match {
3024                scrutinee: Box::new(scrutinee),
3025                arms,
3026            },
3027            result,
3028            span,
3029        )
3030    }
3031
3032    /// `Circle(r) | Square(r)` — several patterns for one arm.
3033    ///
3034    /// The rule that makes this a *pattern* rather than two arms sharing a body is that every
3035    /// alternative binds the same names at the same types. That is checked here, and then the
3036    /// alternatives' variables are **unified onto the first's**, so the body reads one `r` and the
3037    /// evaluator does not have to say which alternative matched.
3038    fn or_pattern(&mut self, p: &Node, scrut: &Ty, span: Span) -> Pattern {
3039        // `a | b | c` is `(| (| a b) c)`, so the alternatives are collected by flattening rather
3040        // than by a list in the grammar.
3041        let mut nodes = Vec::new();
3042        flatten_alts(p, &mut nodes);
3043
3044        let before = self.locals.len();
3045        let mut alts: Vec<Pattern> = Vec::new();
3046        let mut first: Vec<(Arc<str>, VarId, Ty)> = Vec::new();
3047        for (i, node) in nodes.iter().enumerate() {
3048            self.locals.truncate(before);
3049            let pat = self.pattern(node, scrut);
3050            let bound: Vec<(Arc<str>, VarId, Ty)> = self.locals[before..]
3051                .iter()
3052                .filter_map(|b| match &b.kind {
3053                    BindKind::Local(id, ty) => Some((b.name.clone(), *id, ty.clone())),
3054                    _ => None,
3055                })
3056                .collect();
3057            if i == 0 {
3058                first = bound;
3059                alts.push(pat);
3060                continue;
3061            }
3062            // Same names, or the body would read a variable that is only sometimes bound.
3063            let missing: Vec<&str> = first
3064                .iter()
3065                .filter(|(n, _, _)| !bound.iter().any(|(m, _, _)| m == n))
3066                .map(|(n, _, _)| n.as_ref())
3067                .collect();
3068            let extra: Vec<&str> = bound
3069                .iter()
3070                .filter(|(n, _, _)| !first.iter().any(|(m, _, _)| m == n))
3071                .map(|(n, _, _)| n.as_ref())
3072                .collect();
3073            if !missing.is_empty() || !extra.is_empty() {
3074                let mut said = Vec::new();
3075                if !missing.is_empty() {
3076                    said.push(format!(
3077                        "this alternative does not bind {}",
3078                        missing.join(", ")
3079                    ));
3080                }
3081                if !extra.is_empty() {
3082                    said.push(format!("only this one binds {}", extra.join(", ")));
3083                }
3084                self.diags.push(
3085                    Diagnostic::error(
3086                        "B0356",
3087                        "the alternatives of an or-pattern bind different names",
3088                        node.span(),
3089                    )
3090                    .with_primary_label(said.join("; "))
3091                    .with_note(
3092                        "every alternative has to bind the same names, because the body reads them \
3093                         without knowing which one matched",
3094                    ),
3095                );
3096            }
3097            // One variable per name, taken from the first alternative — and the types unified, so
3098            // `Circle(r) | Named(r)` over an `Int` and a `Str` is a mismatch rather than a value
3099            // whose type depends on which alternative ran.
3100            let mut rename: BTreeMap<VarId, VarId> = BTreeMap::new();
3101            for (name, id, ty) in &bound {
3102                if let Some((_, target, want)) = first.iter().find(|(n, _, _)| n == name) {
3103                    self.unify(ty, want, node.span(), "an or-pattern's alternatives");
3104                    rename.insert(*id, *target);
3105                }
3106            }
3107            let mut pat = pat;
3108            rename_binders(&mut pat, &rename);
3109            alts.push(pat);
3110        }
3111
3112        // The arm's scope is the first alternative's bindings, which the renaming made the only
3113        // ones any alternative produces.
3114        self.locals.truncate(before);
3115        for (name, id, ty) in first {
3116            self.locals.push(Binding {
3117                name,
3118                scopes: ScopeSet::default(),
3119                kind: BindKind::Local(id, ty),
3120            });
3121        }
3122        let _ = span;
3123        Pattern::Or(alts)
3124    }
3125
3126    /// One pattern, against the type of what it matches.
3127    ///
3128    /// Recursive since nested patterns arrived, so it is **counted**: a pattern is an ordinary
3129    /// `Node` and `Some(Some(Some(…)))` is a call expression like any other, so without the guard
3130    /// this would be a second way past the ceiling
3131    /// [`44`](../../../../../docs/44-wave-0-report.md) put on the front end — which is exactly the
3132    /// shape [`85`](../../../../../docs/85-what-the-generator-found-report.md) found three times.
3133    fn pattern(&mut self, p: &Node, scrut: &Ty) -> Pattern {
3134        let span = p.span();
3135        if !self.enter(span) {
3136            return Pattern::Wildcard;
3137        }
3138        let out = self.pattern_inner(p, scrut, span);
3139        self.nesting.leave();
3140        out
3141    }
3142
3143    fn pattern_inner(&mut self, p: &Node, scrut: &Ty, span: Span) -> Pattern {
3144        if p.has_head("|") && p.args.len() == 2 {
3145            return self.or_pattern(p, scrut, span);
3146        }
3147        if p.has_head("@") && p.args.len() == 2 {
3148            let Some(name) = p.args[0].as_var() else {
3149                self.error("B0358", "the left of `@` is a name", p.args[0].span());
3150                return self.pattern(&p.args[1], scrut);
3151            };
3152            // The inner pattern first, so the name is bound *after* it and shadows nothing the
3153            // pattern itself introduced — and the whole value has the scrutinee's own type.
3154            let inner = self.pattern(&p.args[1], scrut);
3155            let id = self.fresh_var();
3156            self.locals.push(Binding {
3157                name: name.name.clone(),
3158                scopes: name.scopes.clone(),
3159                kind: BindKind::Local(id, scrut.clone()),
3160            });
3161            return Pattern::At {
3162                var: id,
3163                inner: Box::new(inner),
3164            };
3165        }
3166        if let Some(l) = p.as_lit() {
3167            return Pattern::Const(match l {
3168                Lit::Int(i) => Const::Int(*i),
3169                Lit::Float(f) => Const::Float(*f),
3170                Lit::Bool(b) => Const::Bool(*b),
3171                Lit::Str(s) | Lit::Keyword(s) => Const::Str(s.clone()),
3172            });
3173        }
3174        if let Some(s) = p.as_var() {
3175            if s.as_str() == sym::WILDCARD {
3176                return Pattern::Wildcard;
3177            }
3178            // A bare name that is a nullary constructor matches that variant; anything else binds.
3179            if let Some(Binding {
3180                kind: BindKind::Ctor(_, variant),
3181                ..
3182            }) = self.resolve(s).cloned()
3183            {
3184                return Pattern::Ctor {
3185                    variant,
3186                    binds: Vec::new(),
3187                };
3188            }
3189            let id = self.fresh_var();
3190            self.locals.push(Binding {
3191                name: s.name.clone(),
3192                scopes: s.scopes.clone(),
3193                kind: BindKind::Local(id, scrut.clone()),
3194            });
3195            return Pattern::Bind(id);
3196        }
3197
3198        // `[]`, `[x]`, `[first, *rest]` — a list taken apart. The scrutinee decides the element
3199        // type, so the binders need no annotation (`docs/33` §33.5).
3200        if p.is_form(sym::LIST) {
3201            let elem = self.subst.fresh();
3202            self.unify(
3203                scrut,
3204                &Ty::list(elem.clone()),
3205                span,
3206                "a list pattern matches a list",
3207            );
3208            let mut items = Vec::new();
3209            let mut rest = None;
3210            for (i, item) in p.args.iter().enumerate() {
3211                let is_rest = item.is_form(sym::REST) && item.args.len() == 1;
3212                if !is_rest {
3213                    items.push(self.pattern(item, &elem));
3214                    continue;
3215                }
3216                if i + 1 != p.args.len() {
3217                    self.error(
3218                        "B0346",
3219                        "`*rest` has to be the last element of a list pattern",
3220                        item.span(),
3221                    );
3222                    continue;
3223                }
3224                // The tail binds a list rather than matching one, so it takes a name or `_` and
3225                // not a pattern: `[a, *[b, c]]` is `[a, b, c]` spelled twice.
3226                let target = &item.args[0];
3227                rest = Some(match target.as_var() {
3228                    Some(s) if s.as_str() == sym::WILDCARD => None,
3229                    Some(s) => {
3230                        let id = self.fresh_var();
3231                        self.locals.push(Binding {
3232                            name: s.name.clone(),
3233                            scopes: s.scopes.clone(),
3234                            kind: BindKind::Local(id, Ty::list(elem.clone())),
3235                        });
3236                        Some(id)
3237                    }
3238                    None => {
3239                        self.error(
3240                            "B0345",
3241                            "the tail of a list pattern is a name, not a pattern",
3242                            target.span(),
3243                        );
3244                        None
3245                    }
3246                });
3247            }
3248            return Pattern::List { items, rest };
3249        }
3250
3251        if p.is_form(sym::REST) {
3252            self.error(
3253                "B0347",
3254                "`*name` is only meaningful inside a list pattern",
3255                span,
3256            );
3257            return Pattern::Wildcard;
3258        }
3259
3260        let Some(head) = p.head_sym().cloned() else {
3261            self.error("B0342", "unsupported pattern", span);
3262            return Pattern::Wildcard;
3263        };
3264        let Some(Binding {
3265            kind: BindKind::Ctor(union, variant),
3266            ..
3267        }) = self.resolve(&head).cloned()
3268        else {
3269            self.error("B0343", format!("`{head}` is not a constructor"), span);
3270            return Pattern::Wildcard;
3271        };
3272        let fields = match self.types.get(&union) {
3273            Some(TyDecl::Union { variants, .. }) => variants
3274                .iter()
3275                .find(|v| v.name == variant)
3276                .map(|v| v.fields.clone())
3277                .unwrap_or_default(),
3278            _ => Vec::new(),
3279        };
3280        let field_tys = self.variant_field_types(scrut, &union, &fields);
3281
3282        let mut binds = Vec::new();
3283        for (i, arg) in p.args.iter().enumerate() {
3284            // `Added(id, text)` binds by position; `Added(text=t)` by name.
3285            let (field_name, target) = if arg.is_form(sym::KW_ARG) && arg.args.len() == 2 {
3286                (arg.args[0].as_var().map(|s| s.name.clone()), &arg.args[1])
3287            } else {
3288                (fields.get(i).map(|(n, _)| n.clone()), arg)
3289            };
3290            let Some(field_name) = field_name else {
3291                self.error("B0344", "cannot tell which field this binds", arg.span());
3292                continue;
3293            };
3294            let ty = field_tys
3295                .get(&field_name)
3296                .cloned()
3297                .unwrap_or_else(|| self.subst.fresh());
3298            binds.push((field_name, self.pattern(target, &ty)));
3299        }
3300        Pattern::Ctor { variant, binds }
3301    }
3302
3303    /// Instantiate a variant's declared field types against the scrutinee's type arguments.
3304    fn variant_field_types(
3305        &mut self,
3306        scrut: &Ty,
3307        union: &str,
3308        fields: &[(Arc<str>, Ty)],
3309    ) -> BTreeMap<Arc<str>, Ty> {
3310        // The scrutinee's own arguments: matching `Leaf(v)` against a `Tree[Str]` binds `v: Str`.
3311        let mut args: Vec<Ty> = Vec::new();
3312        if let Ty::Con(name, xs) = self.subst.resolve(scrut) {
3313            if name.as_ref() == union {
3314                args = xs;
3315            }
3316        }
3317        fields
3318            .iter()
3319            .map(|(n, t)| (n.clone(), ty::instantiate_decl(t, &args)))
3320            .collect()
3321    }
3322
3323    fn record_lit(&mut self, n: &Node, expected: Option<&Ty>, span: Span) -> Core {
3324        // `{}` and `{k: v}` are a *map* when that is what the context wants — `State(todos={})`
3325        // builds an empty `Map[Id, Todo]`, not a record with no fields.
3326        if let Some(Ty::Con(name, args)) = expected.map(|t| self.subst.resolve(t)) {
3327            if name.as_ref() == Ty::MAP && args.len() == 2 {
3328                let mut pairs = Vec::new();
3329                for pair in n.args.chunks(2) {
3330                    if pair.len() != 2 {
3331                        break;
3332                    }
3333                    let kc = self.expr(&pair[0], Some(&args[0]));
3334                    self.unify(&kc.ty, &args[0], kc.span, "map key");
3335                    let vc = self.expr(&pair[1], Some(&args[1]));
3336                    self.unify(&vc.ty, &args[1], vc.span, "map value");
3337                    pairs.push((kc, vc));
3338                }
3339                return Core::new(
3340                    CoreKind::MapLit(pairs),
3341                    Ty::map(args[0].clone(), args[1].clone()),
3342                    span,
3343                );
3344            }
3345        }
3346        let Some(model) = expected
3347            .map(|t| self.subst.resolve(t))
3348            .and_then(|t| t.con_name().map(Arc::<str>::from))
3349        else {
3350            self.error("B0346", "cannot tell which model this record builds", span);
3351            return Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span);
3352        };
3353        let mut args: Vec<Node> = Vec::new();
3354        for pair in n.args.chunks(2) {
3355            if pair.len() != 2 {
3356                break;
3357            }
3358            let key = pair[0].as_keyword().unwrap_or("?");
3359            args.push(Node::form(
3360                sym::KW_ARG,
3361                vec![Node::sym(key, pair[0].span()), pair[1].clone()],
3362                pair[1].span(),
3363            ));
3364        }
3365        self.make(&model, None, &args, span)
3366    }
3367
3368    fn dot(&mut self, n: &Node, span: Span) -> Core {
3369        let base = self.expr(&n.args[0], None);
3370        let Some(name) = n.args[1].as_var().map(|s| s.name.clone()) else {
3371            self.error("B0347", "expected a field or method name", span);
3372            return Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span);
3373        };
3374        let rest = &n.args[2..];
3375
3376        // `t.with(done=…)` — functional record update.
3377        if name.as_ref() == "with" {
3378            let base_ty = self.subst.resolve(&base.ty);
3379            let field_tys = self.model_fields(&base_ty);
3380            let mut fields = Vec::new();
3381            for a in rest {
3382                if !a.is_form(sym::KW_ARG) || a.args.len() != 2 {
3383                    self.error("B0348", "`with` takes named fields", a.span());
3384                    continue;
3385                }
3386                let Some(fname) = a.args[0].as_var().map(|s| s.name.clone()) else {
3387                    continue;
3388                };
3389                let want = field_tys.get(&fname).cloned();
3390                let value = self.expr(&a.args[1], want.as_ref());
3391                match want {
3392                    Some(w) => self.unify(&value.ty, &w, value.span, &format!("field `{fname}`")),
3393                    None => self.error(
3394                        "B0349",
3395                        format!("no field `{fname}` on `{base_ty}`"),
3396                        a.span(),
3397                    ),
3398                }
3399                fields.push((fname, value));
3400            }
3401            let ty = base.ty.clone();
3402            return Core::new(
3403                CoreKind::With {
3404                    base: Box::new(base),
3405                    fields,
3406                },
3407                ty,
3408                span,
3409            );
3410        }
3411
3412        // A plain field read.
3413        if rest.is_empty() {
3414            let base_ty = self.subst.resolve(&base.ty);
3415            if let Some(ty) = self.model_fields(&base_ty).get(&name).cloned() {
3416                return Core::new(
3417                    CoreKind::Field {
3418                        base: Box::new(base),
3419                        name,
3420                    },
3421                    ty,
3422                    span,
3423                );
3424            }
3425        }
3426
3427        // Otherwise it is uniform function-call syntax: `xs.map_list(f)` is `map_list(xs, f)`.
3428        let mut call_args = vec![n.args[0].clone()];
3429        call_args.extend(rest.iter().cloned());
3430        let call = Node::form_sym(
3431            n.args[1]
3432                .head_sym()
3433                .cloned()
3434                .unwrap_or_else(|| Symbol::new(&name)),
3435            call_args,
3436            span,
3437        );
3438        if self.resolve(&Symbol::new(&name)).is_some() {
3439            return self.call(&call, None, span);
3440        }
3441        let base_ty = self.subst.resolve(&base.ty);
3442        self.error(
3443            "B0350",
3444            format!("no field or function `{name}` for `{base_ty}`"),
3445            span,
3446        );
3447        Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span)
3448    }
3449
3450    fn model_fields(&self, ty: &Ty) -> BTreeMap<Arc<str>, Ty> {
3451        let Some(name) = ty.con_name() else {
3452            return BTreeMap::new();
3453        };
3454        match self.types.get(name) {
3455            Some(TyDecl::Model { fields, .. }) => {
3456                let args: &[Ty] = match ty {
3457                    Ty::Con(_, args) => args,
3458                    _ => &[],
3459                };
3460                fields
3461                    .iter()
3462                    .map(|(n, t)| (n.clone(), ty::instantiate_decl(t, args)))
3463                    .collect()
3464            }
3465            Some(TyDecl::Newtype { inner, .. }) => {
3466                BTreeMap::from([(Arc::from("value"), inner.clone())])
3467            }
3468            _ => BTreeMap::new(),
3469        }
3470    }
3471
3472    fn call(&mut self, n: &Node, expected: Option<&Ty>, span: Span) -> Core {
3473        let head = n.head_sym().cloned().unwrap_or_else(|| Symbol::new("?"));
3474
3475        // `(call callee args...)` — a computed callee.
3476        if head.as_str() == sym::CALL && !n.args.is_empty() {
3477            let func = self.expr(&n.args[0], None);
3478            return self.apply_fn(func, &n.args[1..], span);
3479        }
3480
3481        match self.resolve(&head).cloned().map(|b| b.kind) {
3482            Some(BindKind::Prim(p)) => self.prim_call(p, &n.args, expected, span),
3483            Some(BindKind::TraitMethod(m)) => self.trait_call(&m, &n.args, span),
3484            Some(BindKind::Ctor(union, variant)) => {
3485                self.make(&union, Some(&variant), &n.args, span)
3486            }
3487            Some(BindKind::Model(model)) => self.make(&model, None, &n.args, span),
3488            Some(BindKind::Global(name)) => {
3489                if let Some(specs) = self.dicts.get(&name).cloned() {
3490                    return self.apply_bounded(&name, &specs, &n.args, expected, span);
3491                }
3492                let ty = self
3493                    .schemes
3494                    .get(&name)
3495                    .map(|sc| self.subst.instantiate(sc))
3496                    .unwrap_or_else(|| self.subst.fresh());
3497                let func = Core::new(CoreKind::Global(name), ty, span);
3498                self.apply_fn(func, &n.args, span)
3499            }
3500            Some(BindKind::Local(id, ty)) => {
3501                let func = Core::new(CoreKind::Var(id), ty, span);
3502                self.apply_fn(func, &n.args, span)
3503            }
3504            None => {
3505                self.error("B0340", format!("cannot find `{head}` in this scope"), span);
3506                Core::new(CoreKind::Const(Const::Unit), self.subst.fresh(), span)
3507            }
3508        }
3509    }
3510
3511    /// [`Checker::apply_fn`] where one argument has already been checked.
3512    ///
3513    /// A trait call has to type its receiver *before* it can tell which function is being called,
3514    /// so by the time there is a callee that argument is a `Core` and not a `Node`. Re-checking it
3515    /// would report anything wrong with it twice.
3516    fn apply_fn_with(
3517        &mut self,
3518        func: Core,
3519        done: Core,
3520        at: usize,
3521        args: &[Node],
3522        span: Span,
3523    ) -> Core {
3524        let ftype = self.subst.resolve(&func.ty);
3525        let Ty::Fun(param_tys, ret, latent) = ftype else {
3526            return self.apply_fn(func, args, span);
3527        };
3528        self.perform(&latent);
3529        if args.len() != param_tys.len() {
3530            self.error(
3531                "B0351",
3532                format!(
3533                    "expected {} argument(s), got {}",
3534                    param_tys.len(),
3535                    args.len()
3536                ),
3537                span,
3538            );
3539        }
3540        if let Some(want) = param_tys.get(at) {
3541            self.unify(&done.ty, want, done.span, "receiver");
3542        }
3543        let mut checked = Vec::with_capacity(args.len());
3544        for (i, a) in args.iter().enumerate() {
3545            if i == at {
3546                checked.push(done.clone());
3547                continue;
3548            }
3549            let one = self.check_args(std::slice::from_ref(a), &param_tys[i..]);
3550            checked.extend(one);
3551        }
3552        Core::new(
3553            CoreKind::App {
3554                func: Box::new(func),
3555                args: checked,
3556            },
3557            *ret,
3558            span,
3559        )
3560    }
3561
3562    fn apply_fn(&mut self, func: Core, args: &[Node], span: Span) -> Core {
3563        let ftype = self.subst.resolve(&func.ty);
3564        let (param_tys, ret, latent) = match &ftype {
3565            Ty::Fun(ps, r, row) => (ps.clone(), (**r).clone(), row.clone()),
3566            _ => {
3567                let ps: Vec<Ty> = args.iter().map(|_| self.subst.fresh()).collect();
3568                let r = self.subst.fresh();
3569                let row = self.subst.fresh_row();
3570                self.unify(
3571                    &func.ty,
3572                    &Ty::fun_eff(ps.clone(), r.clone(), row.clone()),
3573                    span,
3574                    "callee",
3575                );
3576                (ps, r, row)
3577            }
3578        };
3579        // §3.2's inference, in one line: applying a function performs its row.
3580        self.perform(&latent);
3581        if args.len() != param_tys.len() {
3582            self.error(
3583                "B0351",
3584                format!(
3585                    "expected {} argument(s), got {}",
3586                    param_tys.len(),
3587                    args.len()
3588                ),
3589                span,
3590            );
3591        }
3592        let checked = self.check_args(args, &param_tys);
3593        Core::new(
3594            CoreKind::App {
3595                func: Box::new(func),
3596                args: checked,
3597            },
3598            ret,
3599            span,
3600        )
3601    }
3602
3603    fn check_args(&mut self, args: &[Node], param_tys: &[Ty]) -> Vec<Core> {
3604        args.iter()
3605            .enumerate()
3606            .map(|(i, a)| {
3607                let a = if a.is_form(sym::KW_ARG) && a.args.len() == 2 {
3608                    &a.args[1]
3609                } else {
3610                    a
3611                };
3612                let want = param_tys.get(i).cloned();
3613                let c = self.expr(a, want.as_ref());
3614                if let Some(w) = want {
3615                    self.unify(&c.ty, &w, c.span, "argument");
3616                }
3617                c
3618            })
3619            .collect()
3620    }
3621
3622    fn prim_call(&mut self, p: Prim, args: &[Node], _expected: Option<&Ty>, span: Span) -> Core {
3623        let (_, scheme) = self.prims.get(p.name()).cloned().expect("prim registered");
3624        let ty = self.subst.instantiate(&scheme);
3625        let Ty::Fun(param_tys, ret, latent) = ty else {
3626            self.error("B0352", format!("`{}` is not callable", p.name()), span);
3627            return Core::new(CoreKind::Const(Const::Unit), Ty::unit(), span);
3628        };
3629        if args.len() != param_tys.len() {
3630            self.error(
3631                "B0351",
3632                format!(
3633                    "`{}` takes {} argument(s), got {}",
3634                    p.name(),
3635                    param_tys.len(),
3636                    args.len()
3637                ),
3638                span,
3639            );
3640        }
3641
3642        // §3.7's determinism rule: "the checker therefore rejects `now()`, `rand()`, `uuid()` and
3643        // any I/O **inside a fold** — time is data on the envelope".
3644        if matches!(p, Prim::NewUuid | Prim::Now) && self.in_fold {
3645            self.diags.push(
3646                Diagnostic::error(
3647                    "B0360",
3648                    format!("`{}()` cannot be called inside a fold", p.name()),
3649                    span,
3650                )
3651                .with_primary_label("this would make replay non-deterministic")
3652                .with_note(
3653                    "a fold must be replay-pure: time is data on the envelope (`env.at`), and \
3654                     entity ids are minted at the edge",
3655                )
3656                .with_fix("mint the id in the client's command and read it from the event"),
3657            );
3658        }
3659
3660        let was_in_fold = self.in_fold;
3661        if p == Prim::Fold {
3662            self.in_fold = true;
3663        }
3664        let checked = self.check_args(args, &param_tys);
3665        self.in_fold = was_in_fold;
3666        // Charged *after* the arguments, so that a row variable in the scheme (`map_list`'s `e`)
3667        // has already absorbed whatever the function argument does.
3668        self.perform(&latent);
3669        if p == Prim::HttpFetch {
3670            self.outbound_host(checked.first(), span);
3671        }
3672        if let Some(core) = short_circuit(p, &checked, (*ret).clone(), span) {
3673            return core;
3674        }
3675
3676        Core::new(
3677            CoreKind::Prim {
3678                op: p,
3679                args: checked,
3680            },
3681            *ret,
3682            span,
3683        )
3684    }
3685
3686    /// Charge `net.out(host)` for an `http_fetch`, from the host written at the call site.
3687    ///
3688    /// This is the second place the compiler reads an *argument* to decide a row — `raise` is the
3689    /// first — and the reason is not symmetry. §6.5 derives the egress NetworkPolicy from the
3690    /// program's `net.out` atoms and nothing else, so a host that arrived in a variable would be a
3691    /// call the cluster could not be told about. Requiring a literal is what makes the derivation
3692    /// total: every outbound call in the program names its peer, and the policy is the list.
3693    ///
3694    /// What a program does instead of computing a host is compute everything *else* — the path,
3695    /// the port, the body, the headers — or write one call site per host. A wrapper is still
3696    /// possible in the direction that matters: a higher-order helper takes a closure, and the
3697    /// closure names the host, so its row carries the atom out (§3.2's `e`).
3698    fn outbound_host(&mut self, arg: Option<&Core>, span: Span) {
3699        let (Some(arg), Some(host)) = (arg, arg.and_then(crate::core::literal_str)) else {
3700            let at = arg.map(|a| a.span).unwrap_or(span);
3701            self.diags.push(
3702                Diagnostic::error(
3703                    "B0395",
3704                    "the host of an outbound call has to be written at the call site".to_string(),
3705                    at,
3706                )
3707                .with_primary_label("this is computed, so nothing knows which host it reaches")
3708                .with_note(
3709                    "`http_fetch` performs `net.out(host)`, and the cluster's egress policy is \
3710                     that atom (§6.5). A host that is not written here is a call the deployment \
3711                     cannot be told about",
3712                )
3713                .with_fix(
3714                    "write the host as a literal and compute the path instead — or take a \
3715                     closure, so the caller names its own host and the row carries it out",
3716                ),
3717            );
3718            return;
3719        };
3720        // `origin` is the client's own server, and the client's channel to it is the socket the
3721        // runtime already owns. Allowing it here would put an outbound call on the tier that has
3722        // no way to make one.
3723        if host.as_ref() == "origin" {
3724            self.diags.push(
3725                Diagnostic::error(
3726                    "B0396",
3727                    "`origin` is not a host `http_fetch` can call".to_string(),
3728                    arg.span,
3729                )
3730                .with_primary_label("this names the program's own origin")
3731                .with_note(
3732                    "`net.out(origin)` is the one outbound atom a client tier discharges, and a \
3733                     client reaches its server over the command channel rather than by fetching",
3734                )
3735                .with_fix("send a command, or name the service's own host"),
3736            );
3737            return;
3738        }
3739        if !crate::net::is_nameable_host(&host) {
3740            self.diags.push(
3741                Diagnostic::error(
3742                    "B0396",
3743                    format!("`{host}` is not a host `http_fetch` can call"),
3744                    arg.span,
3745                )
3746                .with_primary_label("this is not a name a `uses net.out(…)` clause could write")
3747                .with_note(
3748                    "the host is a DNS name — ASCII labels separated by dots — because it becomes \
3749                     a NetworkPolicy peer. A scheme, a port or a path is not part of it",
3750                )
3751                .with_fix(
3752                    "give the host alone; the port is a field of the request and the path \
3753                     is its own argument",
3754                ),
3755            );
3756            return;
3757        }
3758        self.perform(&Row::of([Effect::NetOut(host)]));
3759    }
3760
3761    /// Build a union variant or a model record from positional or named arguments.
3762    fn make(&mut self, ty_name: &str, variant: Option<&str>, args: &[Node], span: Span) -> Core {
3763        let decl = self.types.get(ty_name).cloned();
3764        let (declared, arity): (Vec<(Arc<str>, Ty)>, usize) = match (&decl, variant) {
3765            (Some(TyDecl::Union { variants, .. }), Some(v)) => {
3766                match variants.iter().find(|x| x.name.as_ref() == v) {
3767                    Some(found) => (found.fields.clone(), found.fields.len()),
3768                    None => {
3769                        self.error("B0353", format!("no variant `{v}` on `{ty_name}`"), span);
3770                        (Vec::new(), 0)
3771                    }
3772                }
3773            }
3774            (Some(TyDecl::Model { fields, .. }), _) => (fields.clone(), fields.len()),
3775            (Some(TyDecl::Newtype { inner, .. }), _) => {
3776                (vec![(Arc::from("value"), inner.clone())], 1)
3777            }
3778            _ => {
3779                self.error("B0354", format!("cannot construct `{ty_name}`"), span);
3780                (Vec::new(), 0)
3781            }
3782        };
3783
3784        // Fresh type arguments for each declared parameter, so `Some(1)` is `Option[Int]`.
3785        //
3786        // The arity comes from the declaration, not from this one variant: `Err` mentions only
3787        // `Result`'s second parameter, and reading the arity off it would build a
3788        // `Result[Rejection]` that then fails to unify with `Result[list[Event], Rejection]`.
3789        let param_count = decl.as_ref().map(|d| d.arity()).unwrap_or(0);
3790        let ty_args: Vec<Ty> = (0..param_count).map(|_| self.subst.fresh()).collect();
3791
3792        if args.len() != arity {
3793            self.error(
3794                "B0351",
3795                format!(
3796                    "`{}` takes {arity} field(s), got {}",
3797                    variant.unwrap_or(ty_name),
3798                    args.len()
3799                ),
3800                span,
3801            );
3802        }
3803
3804        let mut fields = Vec::new();
3805        for (i, a) in args.iter().enumerate() {
3806            let (fname, value_node) = if a.is_form(sym::KW_ARG) && a.args.len() == 2 {
3807                (a.args[0].as_var().map(|s| s.name.clone()), &a.args[1])
3808            } else {
3809                (declared.get(i).map(|(n, _)| n.clone()), a)
3810            };
3811            let Some(fname) = fname else {
3812                self.error("B0344", "cannot tell which field this sets", a.span());
3813                continue;
3814            };
3815            let want = declared
3816                .iter()
3817                .find(|(n, _)| *n == fname)
3818                .map(|(_, t)| ty::instantiate_decl(t, &ty_args));
3819            let value = self.expr(value_node, want.as_ref());
3820            match want {
3821                Some(w) => self.unify(&value.ty, &w, value.span, &format!("field `{fname}`")),
3822                None => self.error(
3823                    "B0349",
3824                    format!("no field `{fname}` on `{}`", variant.unwrap_or(ty_name)),
3825                    a.span(),
3826                ),
3827            }
3828            fields.push((fname, value));
3829        }
3830
3831        Core::new(
3832            CoreKind::Make {
3833                ty: Arc::from(ty_name),
3834                variant: variant.map(Arc::from),
3835                fields,
3836            },
3837            Ty::Con(Arc::from(ty_name), ty_args),
3838            span,
3839        )
3840    }
3841}
3842
3843/// A dotted or applied node, back as the text someone wrote — `net.out(api.example.com)`.
3844fn written_form(n: &Node) -> Option<String> {
3845    if let Some(s) = n.as_var() {
3846        return Some(s.as_str().to_string());
3847    }
3848    if let Some(s) = n.as_str_lit() {
3849        return Some(s.to_string());
3850    }
3851    if n.is_form(sym::DOT) && n.args.len() >= 2 {
3852        let base = written_form(&n.args[0])?;
3853        let field = n.args[1].as_var()?.as_str().to_string();
3854        let rest = &n.args[2..];
3855        if rest.is_empty() {
3856            return Some(format!("{base}.{field}"));
3857        }
3858        let args: Vec<String> = rest.iter().filter_map(written_form).collect();
3859        return Some(format!("{base}.{field}({})", args.join(", ")));
3860    }
3861    let head = n.head_name()?;
3862    if n.args.is_empty() {
3863        return Some(head.to_string());
3864    }
3865    let args: Vec<String> = n.args.iter().filter_map(written_form).collect();
3866    Some(format!("{head}({})", args.join(", ")))
3867}
3868
3869/// Every `Core` a clause holds, for the resolution pass.
3870fn clause_cores_mut(c: &mut crate::testing::Clause) -> Vec<&mut Core> {
3871    use crate::testing::{Clause, Count, Expectation};
3872    match c {
3873        Clause::Given { events, .. } => vec![events],
3874        Clause::When { commands, .. } => commands.iter_mut().collect(),
3875        Clause::Stub { value, .. } => vec![value],
3876        Clause::Expect { what, .. } => match what {
3877            Expectation::Holds(e) => vec![e],
3878            Expectation::PageContains { needle, .. } => vec![needle],
3879            Expectation::FoldEquals { events, .. } => vec![events],
3880            Expectation::Performed {
3881                how: Count::With(e),
3882                ..
3883            } => vec![e],
3884            _ => Vec::new(),
3885        },
3886    }
3887}
3888
3889/// Parameter names for a builtin type constructor, which has an arity and no declaration.
3890///
3891/// `a`, `b`, … — the names the generated reference already renders `list[a]` and `Map[a, b]` with,
3892/// so a suggestion and the reference agree about what to call the thing in the brackets.
3893fn letters(n: usize) -> Vec<Arc<str>> {
3894    (0..n)
3895        .map(|i| Arc::from(((b'a' + i as u8) as char).to_string().as_str()))
3896        .collect()
3897}
3898
3899/// Walk a `Core` tree applying the final substitution to every recorded type.
3900/// The name a `raises(...)` atom carries, for the type of a raised value.
3901///
3902/// A declared type, and not a builtin: `raise 4` would give a handler nothing to say it catches,
3903/// and `raises(Int)` would make every integer failure in a program the same failure. A `list[E]`
3904/// is refused for the same reason — the atom names a constructor, so `list` would be the name and
3905/// two unrelated lists would collide.
3906fn error_ty_name(t: &Ty) -> Option<Arc<str>> {
3907    match t {
3908        Ty::Con(name, args) if args.is_empty() => match name.as_ref() {
3909            Ty::INT | Ty::FLOAT | Ty::BOOL | Ty::STR | Ty::UNIT => None,
3910            _ => Some(name.clone()),
3911        },
3912        _ => None,
3913    }
3914}
3915
3916/// Could a second child of the same `parallel:` scope tell that this one had run?
3917///
3918/// The list is the atoms that **write** state the program or its own substrate holds: the log, the
3919/// document, the merge point, a file and an external store. Two children touching any of them would
3920/// make the scope's answer a function of which ran first, and the whole claim for the form is that
3921/// it is not. A *read* of any of them is fine, because nothing in the scope writes it.
3922///
3923/// What is deliberately absent is as much of the argument as what is present:
3924///
3925/// * **`net.out(host)`** — a remote host's state is not Beck's to order, and §3.2 never claimed it
3926///   was. Two outbound calls are the case this form exists for, so refusing them would leave the
3927///   feature with nothing to do.
3928/// * **`nondet`** — a clock or a fresh id is a *read* of something outside the program. Two
3929///   children reading the clock do not interfere; they already disagree when run in sequence, and
3930///   §3.7 is what keeps them out of a fold.
3931/// * **`raises(E)`** and **`partial`** — failing is control flow, and the scope's join is what
3932///   orders it (see [`Checker::parallel_expr`]).
3933/// * **`cap.*`** — an authority the caller holds, not state a child writes.
3934/// * **`fs.read(path)`** — a read of a file no child of this scope writes, since `fs.write` is
3935///   refused. [`docs/80`](../../../../../docs/80-a-scope-owns-its-children-report.md) §80.2 had to
3936///   refuse the pair because `fs(path)` was one atom;
3937///   [`docs/81`](../../../../../docs/81-fs-is-two-atoms-report.md) split it, and this line is what
3938///   the split was for.
3939/// * **`external.read(store)`** — the same argument, and it needed no change because §3.8's
3940///   escape hatches were two atoms from the start.
3941fn observable_order(e: &Effect) -> bool {
3942    matches!(
3943        e,
3944        Effect::Ingress
3945            | Effect::Durable
3946            | Effect::Dom
3947            | Effect::FsWrite(_)
3948            | Effect::ExternalWrite(_)
3949    )
3950}
3951
3952fn resolve_types(c: &mut Core, s: &Subst) {
3953    c.ty = s.resolve(&c.ty);
3954    match &mut c.kind {
3955        CoreKind::Lam { body, .. } => resolve_types(std::sync::Arc::make_mut(body), s),
3956        CoreKind::App { func, args } => {
3957            resolve_types(func, s);
3958            for a in args {
3959                resolve_types(a, s);
3960            }
3961        }
3962        CoreKind::Prim { args, .. } => {
3963            for a in args {
3964                resolve_types(a, s);
3965            }
3966        }
3967        CoreKind::Let { value, body, .. } => {
3968            resolve_types(value, s);
3969            resolve_types(body, s);
3970        }
3971        CoreKind::If { cond, then, alt } => {
3972            resolve_types(cond, s);
3973            resolve_types(then, s);
3974            resolve_types(alt, s);
3975        }
3976        CoreKind::Match { scrutinee, arms } => {
3977            resolve_types(scrutinee, s);
3978            for a in arms {
3979                resolve_types(&mut a.body, s);
3980            }
3981        }
3982        CoreKind::Make { fields, .. } | CoreKind::With { fields, .. } => {
3983            for (_, f) in fields {
3984                resolve_types(f, s);
3985            }
3986        }
3987        CoreKind::Field { base, .. } => resolve_types(base, s),
3988        CoreKind::ListLit(xs) => {
3989            for x in xs {
3990                resolve_types(x, s);
3991            }
3992        }
3993        CoreKind::MapLit(kvs) => {
3994            for (k, v) in kvs {
3995                resolve_types(k, s);
3996                resolve_types(v, s);
3997            }
3998        }
3999        CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => {}
4000    }
4001    if let CoreKind::With { base, .. } = &mut c.kind {
4002        resolve_types(base, s);
4003    }
4004}
4005
4006/// Every row variable written into a type, in no particular order.
4007fn row_vars_of(t: &Ty, out: &mut Vec<RowVarId>) {
4008    match t {
4009        Ty::Var(_) => {}
4010        Ty::Con(_, args) => {
4011            for a in args {
4012                row_vars_of(a, out);
4013            }
4014        }
4015        Ty::Fun(ps, r, row) => {
4016            for p in ps {
4017                row_vars_of(p, out);
4018            }
4019            row_vars_of(r, out);
4020            out.extend(row.tails.iter().copied());
4021        }
4022    }
4023}
4024
4025#[cfg(test)]
4026mod tests {
4027    use crate::check_str;
4028    use crate::ty::Effect;
4029
4030    /// The row inferred for a definition, as printed atom names.
4031    fn row_of(src: &str, name: &str) -> Vec<String> {
4032        let (program, d, map) = check_str("t.beck", src);
4033        assert!(
4034            !d.iter()
4035                .any(|x| x.code.starts_with("B03") && x.code != "B0370"),
4036            "{}",
4037            d.render(&map)
4038        );
4039        program
4040            .defs
4041            .get(name)
4042            .unwrap_or_else(|| panic!("no `{name}` in {:?}", program.defs.keys()))
4043            .effects
4044            .iter()
4045            .map(|e| e.name())
4046            .collect()
4047    }
4048
4049    fn codes(src: &str) -> Vec<&'static str> {
4050        let (_, d, _) = check_str("t.beck", src);
4051        d.iter().map(|x| x.code).collect()
4052    }
4053
4054    /// A flat body of `n` sequential bindings — the shape `docs/64` §64.4 found unbounded.
4055    fn flat_body(n: usize) -> String {
4056        let mut src = String::from("def f() -> Int:\n");
4057        for i in 0..n {
4058            src.push_str(&format!("    v{i} = {i}\n"));
4059        }
4060        src.push_str("    return v0\n");
4061        src
4062    }
4063
4064    /// The refusal is a diagnostic, and it is the *same* diagnostic in every build.
4065    ///
4066    /// `docs/64` §64.4 measured what it was before: `thread 'beck-eval' has overflowed its stack`,
4067    /// SIGABRT, no span, at 12,000 bindings in a debug build and 100,000 in a release one — so
4068    /// "does this program compile" was a question about how the compiler was built. That is the
4069    /// property `adr/0007` established a ceiling must never have.
4070    /// Every check here runs on the **declared front-end stack**, because that is the stack the
4071    /// ceiling was sized against. A default test thread has 2 MiB and the ceiling is worth 14 MiB
4072    /// of frames, so a test that called `check_str` directly would abort — which is how CI found
4073    /// this, the first version of these two having been written without it.
4074    fn block_codes(src: &str) -> Vec<&'static str> {
4075        beck_diag::depth::on_the_front_end_stack(|| {
4076            let (_, d, _) = check_str("t.beck", src);
4077            d.iter().map(|x| x.code).collect()
4078        })
4079    }
4080
4081    #[test]
4082    fn a_block_past_the_ceiling_is_refused_with_a_diagnostic() {
4083        let over = beck_diag::depth::MAX_BLOCK as usize + 8;
4084        let found = block_codes(&flat_body(over));
4085        assert!(found.contains(&"B0389"), "{found:?}");
4086        // …and one diagnostic, not one per statement on the way out.
4087        assert_eq!(found.iter().filter(|c| **c == "B0389").count(), 1);
4088    }
4089
4090    /// A long body that is *under* the ceiling still checks, which is the half that says the
4091    /// number is a backstop rather than an opinion about style.
4092    #[test]
4093    fn a_long_block_under_the_ceiling_is_ordinary() {
4094        let found = block_codes(&flat_body(beck_diag::depth::MAX_BLOCK as usize - 8));
4095        assert!(found.is_empty(), "{found:?}");
4096    }
4097
4098    /// The pair `adr/0012` established for the nesting axis, for this one: **measure** the bytes a
4099    /// level costs and fail if the declaration has stopped covering the ceiling.
4100    ///
4101    /// The probe runs on a stack far larger than the one whose adequacy is being concluded, so the
4102    /// measurement is never the thing that overflows.
4103    #[test]
4104    fn the_block_ceiling_fits_the_declared_stack() {
4105        const PROBE: usize = 400;
4106        let spent = std::thread::Builder::new()
4107            .stack_size(256 * 1024 * 1024)
4108            .spawn(|| {
4109                let src = flat_body(PROBE);
4110                beck_diag::depth::probe::stack_spent(|| check_str("probe.beck", &src))
4111            })
4112            .expect("a thread")
4113            .join()
4114            .expect("the probe checks");
4115
4116        let per_level = spent / PROBE;
4117        println!("checker: {spent} bytes for {PROBE} statements ({per_level} per statement)");
4118        // Twice over, as the parser's and the evaluator's are: whoever drives the checker has as
4119        // much stack again above the ceiling as the ceiling itself needs.
4120        let needed = beck_diag::depth::MAX_BLOCK as usize * per_level * 2;
4121        assert!(
4122            needed < beck_diag::depth::STACK_BYTES,
4123            "a ceiling of {} statements at {per_level} bytes each needs {needed} bytes with the \
4124             margin, against a declared STACK_BYTES of {} — raise the declaration or lower the \
4125             ceiling",
4126            beck_diag::depth::MAX_BLOCK,
4127            beck_diag::depth::STACK_BYTES
4128        );
4129    }
4130
4131    #[test]
4132    fn an_effect_reached_through_an_undeclared_function_is_still_inferred() {
4133        // This is the case Phase 1 could not see. Its collection consulted each global's
4134        // *declared* effects, so an intermediate that declared nothing hid everything behind it —
4135        // and `mint` declares nothing, because inference is the point of not having to.
4136        let src = "\
4137def mint() -> Str:
4138    return uuid()
4139
4140def label(prefix: Str) -> Str:
4141    return prefix + mint()
4142";
4143        assert_eq!(row_of(src, "mint"), ["nondet"]);
4144        assert_eq!(
4145            row_of(src, "label"),
4146            ["nondet"],
4147            "an effect must travel as far as the calls do"
4148        );
4149    }
4150
4151    #[test]
4152    fn referencing_a_function_performs_nothing_but_applying_it_performs_everything() {
4153        // The distinction that makes inference different from collection — and the reason
4154        // `fold(apply_event, …)` is a pure expression even when `apply_event` is not.
4155        let src = "\
4156def mint() -> Str:
4157    return uuid()
4158
4159def names() -> list[Str]:
4160    return map_list([\"a\"], lambda x: x)
4161
4162def held() -> (Str) -> Str:
4163    return lambda x: x + mint()
4164
4165def used() -> Str:
4166    return mint()
4167";
4168        assert!(row_of(src, "names").is_empty());
4169        assert!(
4170            row_of(src, "held").is_empty(),
4171            "returning a function that would mint an id mints nothing"
4172        );
4173        assert_eq!(row_of(src, "used"), ["nondet"]);
4174    }
4175
4176    #[test]
4177    fn effect_polymorphism_carries_a_lambdas_row_through_map_list() {
4178        // §3.2's `map : (list[a], (a -> b ! e)) -> list[b] ! e`, from the caller's side: mapping an
4179        // effectful function is effectful, and mapping a pure one is not — with one `map_list`.
4180        let src = "\
4181def pure_labels(xs: list[Str]) -> list[Str]:
4182    return map_list(xs, lambda x: x + \"!\")
4183
4184def minted_labels(xs: list[Str]) -> list[Str]:
4185    return map_list(xs, lambda x: x + uuid())
4186";
4187        assert!(row_of(src, "pure_labels").is_empty());
4188        assert_eq!(row_of(src, "minted_labels"), ["nondet"]);
4189    }
4190
4191    /// §3.2's `map : (list[a], (a -> b ! e)) -> list[b] ! e`, for a definition a *user* wrote.
4192    ///
4193    /// A parameter's row is quantified in the definition's scheme, so each call site instantiates
4194    /// its own: a caller that passes a pure function is pure whatever another caller passes.
4195    /// `docs/33` §33.2 has why a shared variable was both sound and wrong.
4196    #[test]
4197    fn a_user_higher_order_function_is_polymorphic_over_its_arguments_row() {
4198        let src = "\
4199def apply(f: (Str) -> Str, x: Str) -> Str:
4200    return f(x)
4201
4202def pure_use() -> Str:
4203    return apply(lambda s: s, \"a\")
4204
4205def impure_use() -> Str:
4206    return apply(lambda s: s + uuid(), \"b\")
4207";
4208        assert!(
4209            row_of(src, "apply").is_empty(),
4210            "`apply` performs nothing of its own: {:?}",
4211            row_of(src, "apply")
4212        );
4213        assert!(
4214            row_of(src, "pure_use").is_empty(),
4215            "and a pure caller stays pure however another caller uses it: {:?}",
4216            row_of(src, "pure_use")
4217        );
4218        assert_eq!(
4219            row_of(src, "impure_use"),
4220            ["nondet"],
4221            "while the effectful caller is charged for exactly what it passed"
4222        );
4223    }
4224
4225    #[test]
4226    fn a_generalised_row_is_still_charged_to_whoever_supplies_it() {
4227        // The other direction, which is the one a mistake here would break silently: the effect has
4228        // to arrive *somewhere*. `render` is a view, so `nondet` reaching it is a placement error
4229        // rather than a lost effect — and the caller is where it lands.
4230        let src = "\
4231def twice(f: (Int) -> Int, n: Int) -> Int:
4232    return f(f(n))
4233
4234def stamped(n: Int) -> Int:
4235    return twice(lambda m: m + now(), n)
4236
4237def plain(n: Int) -> Int:
4238    return twice(lambda m: m + 1, n)
4239";
4240        assert!(row_of(src, "twice").is_empty());
4241        assert_eq!(row_of(src, "stamped"), ["nondet"]);
4242        assert!(row_of(src, "plain").is_empty());
4243    }
4244
4245    #[test]
4246    fn a_quantified_row_is_charged_even_when_the_body_never_calls_the_argument() {
4247        // The over-approximation docs/33 §33.3 names, asserted so that it is a decision rather than
4248        // a surprise. `ignore` never calls `f`, and a caller passing an effectful one is charged
4249        // anyway — because the row is quantified from the *signature*, before any body is read.
4250        //
4251        // It is the safe direction (an effect too many forces a stricter placement; an effect too
4252        // few would let a fold read a clock), and it is the same rule `uses` already followed: "a
4253        // declared effect is part of the signature whether or not the body reaches it".
4254        let src = "\
4255def ignore(xs: list[Int], f: (Int) -> Int) -> Int:
4256    return list_len(xs)
4257
4258def caller(xs: list[Int]) -> Int:
4259    return ignore(xs, lambda n: now())
4260";
4261        assert!(row_of(src, "ignore").is_empty());
4262        assert_eq!(row_of(src, "caller"), ["nondet"]);
4263    }
4264
4265    #[test]
4266    fn a_definition_that_returns_a_function_keeps_the_older_monomorphic_row() {
4267        // The limit docs/33 §33.3 names, asserted rather than described. A row variable that also
4268        // reaches the *return* type is not quantified, because `instantiate` renames syntactic
4269        // occurrences and the return's row is bound to the parameter's through the substitution —
4270        // so one side of the call would be renamed and the other would not.
4271        let src = "\
4272def hold(f: (Int) -> Int) -> (Int) -> Int:
4273    return f
4274
4275def use_pure(n: Int) -> Int:
4276    return hold(lambda m: m + 1)(n)
4277
4278def use_impure(n: Int) -> Int:
4279    return hold(lambda m: m + now())(n)
4280";
4281        assert_eq!(
4282            row_of(src, "use_pure"),
4283            ["nondet"],
4284            "still contaminated, and this is the test that will start failing when it is not"
4285        );
4286    }
4287
4288    #[test]
4289    fn mutual_recursion_needs_no_ordering() {
4290        // `even` calls `odd` calls `even`. A row bound to a row that mentions it resolves to the
4291        // least fixed point rather than diverging, which is why no dependency sort is needed.
4292        let src = "\
4293def ping(n: Int) -> Str:
4294    if n < 1:
4295        return uuid()
4296    return pong(n - 1)
4297
4298def pong(n: Int) -> Str:
4299    return ping(n - 1)
4300";
4301        assert_eq!(row_of(src, "ping"), ["nondet"]);
4302        assert_eq!(row_of(src, "pong"), ["nondet"]);
4303    }
4304
4305    #[test]
4306    fn a_declared_row_is_a_bound_and_exceeding_it_is_an_error() {
4307        // §3.6: "effect widening is a breaking API change". So the compiler will not widen it.
4308        let src = "\
4309def charge(amount: Int) -> Str uses net.out(payments.example.com):
4310    return uuid()
4311";
4312        assert!(codes(src).contains(&"B0370"), "{:?}", codes(src));
4313
4314        // …and declaring it is enough to make it compile.
4315        let ok = "\
4316def charge(amount: Int) -> Str uses net.out(payments.example.com), nondet:
4317    return uuid()
4318";
4319        assert!(!codes(ok).contains(&"B0370"), "{:?}", codes(ok));
4320        let (program, _, _) = check_str("t.beck", ok);
4321        let row = &program.defs["charge"].row;
4322        assert!(row
4323            .atoms
4324            .contains(&Effect::NetOut("payments.example.com".into())));
4325        assert!(row.atoms.contains(&Effect::Nondet));
4326    }
4327
4328    #[test]
4329    fn an_outbound_call_performs_the_host_it_names() {
4330        // The row is *inferred* from the argument — the `uses` clause below is the bound §3.6
4331        // makes it, and the atom in it came from the string on the line above.
4332        let src = "\
4333def fetch_rate() -> Str uses net.out(rates.example.com), raises(HttpError):
4334    r = http_fetch(\"rates.example.com\", HttpRequest(method=\"GET\", path=\"/usd\", headers={}, body=\"\", port=80, tls=False, secrets={}))
4335    return r.body
4336";
4337        assert_eq!(
4338            row_of(src, "fetch_rate"),
4339            ["net.out(rates.example.com)", "raises(HttpError)"]
4340        );
4341    }
4342
4343    #[test]
4344    fn an_outbound_call_to_a_host_it_cannot_name_is_refused() {
4345        let req =
4346            "HttpRequest(method=\"GET\", path=\"/\", headers={}, body=\"\", port=80, tls=False, secrets={})";
4347        // Computed: nothing downstream could write the NetworkPolicy peer.
4348        let computed = format!(
4349            "def go(host: Str) -> Str uses net.out(x.example.com), raises(HttpError):\n    \
4350             return http_fetch(host, {req}).body\n"
4351        );
4352        assert!(
4353            codes(&computed).contains(&"B0395"),
4354            "{:?}",
4355            codes(&computed)
4356        );
4357
4358        // A URL is not a host, and neither is a host with a port on it.
4359        for bad in ["https://x.example.com", "x.example.com:8080", "origin"] {
4360            let src = format!(
4361                "def go() -> Str uses net.out(x.example.com), raises(HttpError):\n    \
4362                 return http_fetch(\"{bad}\", {req}).body\n"
4363            );
4364            assert!(codes(&src).contains(&"B0396"), "{bad}: {:?}", codes(&src));
4365        }
4366    }
4367
4368    #[test]
4369    fn a_declared_effect_survives_an_empty_body() {
4370        // A stub that will phone home later must say so today: otherwise the edit that fills the
4371        // body in silently re-places every caller.
4372        let src = "\
4373def charge(amount: Int) -> Str uses net.out(payments.example.com):
4374    return \"receipt\"
4375";
4376        assert_eq!(row_of(src, "charge"), ["net.out(payments.example.com)"]);
4377    }
4378
4379    #[test]
4380    fn ambient_effects_are_carried_but_never_printed_in_a_signature() {
4381        let src = "\
4382def audit(what: Str) -> Str uses log:
4383    return what
4384";
4385        let (program, _, _) = check_str("t.beck", src);
4386        let def = &program.defs["audit"];
4387        assert_eq!(def.effects, vec![Effect::Ambient(crate::ty::Ambient::Log)]);
4388        assert!(
4389            def.row.visible().is_empty(),
4390            "§3.2 elides the ambient set from signatures"
4391        );
4392    }
4393
4394    // --------------------------------------------------------------- parameterised declarations
4395
4396    const TREE: &str = "\
4397union Tree[T]:
4398    Leaf(value: T)
4399    Node(kids: list[Tree[T]])
4400
4401def count[T](t: Tree[T]) -> Int:
4402    match t:
4403        case Leaf(value):
4404            return 1
4405        case Node(kids):
4406            return list_len(kids)
4407";
4408
4409    #[test]
4410    fn a_declaration_may_take_a_type_parameter_and_mention_itself_under_one() {
4411        assert_eq!(codes(TREE), Vec::<&str>::new());
4412    }
4413
4414    #[test]
4415    fn a_parameterised_declaration_is_a_different_type_at_each_argument() {
4416        // The point of the whole feature, and the thing a compiler that ignored the arguments
4417        // would still compile: `Tree[Int]` and `Tree[Str]` do not unify.
4418        let src = format!(
4419            "{TREE}
4420def ints() -> Tree[Int]:
4421    return Leaf(value=1)
4422
4423def strs() -> Tree[Str]:
4424    return ints()
4425"
4426        );
4427        assert!(codes(&src).contains(&"B0320"), "{:?}", codes(&src));
4428    }
4429
4430    #[test]
4431    fn a_pattern_binds_the_argument_the_scrutinee_carries() {
4432        // `case Leaf(value)` over a `Tree[Str]` binds a `Str`, not the declaration's parameter.
4433        let ok = format!(
4434            "{TREE}
4435def first(t: Tree[Str]) -> Str:
4436    match t:
4437        case Leaf(value):
4438            return value
4439        case Node(kids):
4440            return \"\"
4441"
4442        );
4443        assert_eq!(codes(&ok), Vec::<&str>::new());
4444
4445        let bad = ok.replace("return value", "return value + 1");
4446        assert!(!codes(&bad).is_empty(), "a `Str` is not an `Int`");
4447    }
4448
4449    #[test]
4450    fn a_mention_carries_one_argument_per_declared_parameter() {
4451        for (src, why) in [
4452            ("union Box[T]:\n    Held(value: T)\n\ndef f(b: Box) -> Int:\n    return 1\n", "none"),
4453            (
4454                "union Box[T]:\n    Held(value: T)\n\ndef f(b: Box[Int, Str]) -> Int:\n    return 1\n",
4455                "two",
4456            ),
4457        ] {
4458            assert!(codes(src).contains(&"B0311"), "{why}: {:?}", codes(src));
4459        }
4460    }
4461
4462    /// And the spelling it suggests is a program.
4463    ///
4464    /// The label offered `Set[_]`, and `_` is not a type — so the fix a reader copied out was
4465    /// itself refused, by `B0310`. The declaration wrote a name down; that is the one to hand
4466    /// back, and an `impl` head is where it matters most because it binds its own.
4467    #[test]
4468    fn the_spelling_a_missing_type_argument_suggests_is_one_that_compiles() {
4469        let bare = "\
4470type Set[T] = newtype[Map[T, Bool]]
4471
4472trait Sized:
4473    def size(self) -> Int
4474
4475impl Sized for Set:
4476    def size(self):
4477        return map_len(self.value)
4478";
4479        let (_, d, map) = check_str("t.beck", bare);
4480        let text = d.render(&map);
4481        assert!(text.contains("write `Set[T]`"), "{text}");
4482        assert!(
4483            !text.contains("Set[_]"),
4484            "there is no wildcard type:\n{text}"
4485        );
4486
4487        let fixed = bare.replace("impl Sized for Set:", "impl[T] Sized for Set[T]:");
4488        assert_eq!(
4489            codes(&fixed),
4490            Vec::<&str>::new(),
4491            "the suggestion has to check clean"
4492        );
4493    }
4494
4495    #[test]
4496    fn a_parameter_a_declaration_never_mentions_is_still_a_parameter() {
4497        // Arity is declared, not inferred from the fields that happen to use it — so a phantom
4498        // parameter still distinguishes `Tag[Int]` from `Tag[Str]`, and still has to be written.
4499        let src = "\
4500model Tag[T]:
4501    label: Str
4502
4503def a() -> Tag[Int]:
4504    return Tag(label=\"a\")
4505
4506def b() -> Tag[Str]:
4507    return a()
4508";
4509        assert!(codes(src).contains(&"B0320"), "{:?}", codes(src));
4510        let bare = src.replace("Tag[Int]", "Tag");
4511        assert!(codes(&bare).contains(&"B0311"), "{:?}", codes(&bare));
4512    }
4513
4514    #[test]
4515    fn a_type_parameter_may_not_shadow_a_type_or_repeat_itself() {
4516        let shadow = "model Note:\n    text: Str\n\nmodel Box[Note]:\n    held: Note\n";
4517        assert!(codes(shadow).contains(&"B0314"), "{:?}", codes(shadow));
4518
4519        let repeat = "model Pair[T, T]:\n    a: T\n    b: T\n";
4520        assert!(codes(repeat).contains(&"B0315"), "{:?}", codes(repeat));
4521    }
4522
4523    /// The same rule at the production it was missing — a *declaration* rather than a type
4524    /// parameter (`docs/87` §87.4).
4525    ///
4526    /// Every builtin constructor, because the point of the finding is that the check existed for
4527    /// two of them (`Option` and `Result`, which are prelude declarations and so were covered by
4528    /// B0302's "declared twice") and for none of the other fourteen. A test that named one would
4529    /// be the shape of the gap rather than the shape of the fix, which is `docs/84` §84.5's
4530    /// pattern.
4531    #[test]
4532    fn a_declaration_may_not_take_a_builtin_types_name() {
4533        for name in [
4534            "Int", "Str", "Bool", "Float", "Unit", "Html", "Attr", "list", "Map", "Stream",
4535            "Signal", "Envelope", "secret", "internal", "Option", "Result",
4536        ] {
4537            let src = format!("model {name}:\n    held: Int\n");
4538            assert!(
4539                codes(&src).contains(&"B0317"),
4540                "`model {name}` was accepted: {:?}",
4541                codes(&src)
4542            );
4543            let alias = format!("type {name} = Int\n");
4544            assert!(
4545                codes(&alias).contains(&"B0317"),
4546                "`type {name}` was accepted: {:?}",
4547                codes(&alias)
4548            );
4549        }
4550
4551        // And the builtin still means the builtin afterwards, which is what makes the rest of a
4552        // module's diagnostics readable: refusing the declaration has to leave `Int` alone.
4553        let src = "model Int:\n    held: Int\n\ndef f(n: Int) -> Int:\n    return n + 1\n";
4554        assert_eq!(
4555            codes(src),
4556            vec!["B0317"],
4557            "refusing the declaration should not cascade"
4558        );
4559
4560        // A name that is not a builtin is still a name a program may have.
4561        let fine = "model Note:\n    text: Str\n";
4562        assert!(codes(fine).is_empty(), "{:?}", codes(fine));
4563    }
4564
4565    #[test]
4566    fn a_parameterised_alias_is_expanded_and_applied() {
4567        // An alias names no type of its own, so `Pairs[Int]` has to *be* `list[Map[Int, Int]]` by
4568        // the time anything else sees it — including a mismatch report.
4569        let src = "\
4570type Pairs[T] = list[Map[T, T]]
4571
4572def f(xs: Pairs[Int]) -> Int:
4573    return list_len(xs)
4574
4575def g(xs: Pairs[Str]) -> Int:
4576    return f(xs)
4577";
4578        let (_, d, map) = check_str("t.beck", src);
4579        let text = d.render(&map);
4580        assert!(text.contains("B0320"), "{text}");
4581        assert!(
4582            !text.contains("Pairs"),
4583            "an alias is transparent, so nothing downstream should still be talking about it:\n{text}"
4584        );
4585    }
4586
4587    #[test]
4588    fn a_definitions_parameter_and_a_declarations_parameter_do_not_meet() {
4589        // A `def`'s `T` is rigid and a declaration's is positional. The same letter in both is two
4590        // different things, and the body of the `def` may not assume they are the same.
4591        let src = "\
4592union Box[T]:
4593    Held(value: T)
4594
4595def unwrap[T](b: Box[T]) -> T:
4596    match b:
4597        case Held(value):
4598            return value
4599";
4600        assert_eq!(codes(src), Vec::<&str>::new());
4601
4602        let bad = src.replace("-> T:", "-> Int:");
4603        assert!(!codes(&bad).is_empty(), "a `T` is not an `Int`");
4604    }
4605}
4606
4607/// The checker's half of the front end's recursion bound.
4608///
4609/// The reader's bound does not cover this one. A macro expands into a tree nobody typed, and the
4610/// checker is the first pass to walk it — so it counts for itself, against the same ceiling.
4611#[cfg(test)]
4612mod nesting_tests {
4613    use crate::check_str;
4614    use beck_diag::depth::{MAX_NESTING, STACK_BYTES};
4615
4616    /// A type nested `n` deep — `list[list[…Int…]]` — which is the checker's *other* recursion.
4617    fn nested_type(n: usize) -> String {
4618        let mut ty = String::from("Int");
4619        for _ in 0..n {
4620            ty = format!("list[{ty}]");
4621        }
4622        format!("def f(x: {ty}) -> Int:\n    return 1\n")
4623    }
4624
4625    fn nested_expr(n: usize) -> String {
4626        format!(
4627            "def f() -> Int:\n    return {}1{}\n",
4628            "(".repeat(n),
4629            ")".repeat(n)
4630        )
4631    }
4632
4633    fn codes(src: &str) -> Vec<String> {
4634        beck_diag::depth::on_the_front_end_stack(|| {
4635            let (_, d, _) = check_str("deep.beck", src);
4636            d.iter().map(|x| x.code.to_string()).collect()
4637        })
4638    }
4639
4640    #[test]
4641    fn a_type_past_the_ceiling_is_a_diagnostic_rather_than_an_abort() {
4642        // Either pass may be the one that refuses, exactly as for an expression below. The reader
4643        // gets there first *now*: `docs/85` found `Parser::type_expr` recursing in four places with
4644        // no counter at all, so a deep type used to arrive here and is now refused a stage earlier
4645        // and more cheaply. What this test is for is the property in its name — a diagnostic rather
4646        // than an abort — which is a claim about the front end and not about which half of it.
4647        let found = codes(&nested_type(MAX_NESTING as usize + 8));
4648        assert!(
4649            found.iter().any(|c| c == "B0121" || c == "B0390"),
4650            "expected a nesting refusal from the reader or the checker, got {found:?}"
4651        );
4652    }
4653
4654    #[test]
4655    fn an_expression_past_the_ceiling_is_refused_by_whichever_pass_reaches_it_first() {
4656        // The reader gets there first for an expression, which is the point of bounding all three:
4657        // whichever pass is handed the deep tree is the one that refuses it.
4658        let found = codes(&nested_expr(MAX_NESTING as usize + 8));
4659        assert!(
4660            found.iter().any(|c| c == "B0121" || c == "B0390"),
4661            "expected a nesting refusal, got {found:?}"
4662        );
4663    }
4664
4665    #[test]
4666    fn nesting_a_person_would_write_still_checks() {
4667        assert!(codes(&nested_type(16)).is_empty());
4668    }
4669
4670    #[test]
4671    fn the_ceiling_fits_the_declared_stack() {
4672        const PROBE_DEPTH: usize = 100;
4673        let spent = std::thread::Builder::new()
4674            .stack_size(256 * 1024 * 1024)
4675            .spawn(|| {
4676                let src = nested_type(PROBE_DEPTH);
4677                beck_diag::depth::probe::stack_spent(|| check_str("probe.beck", &src))
4678            })
4679            .expect("a thread")
4680            .join()
4681            .expect("the probe checks");
4682
4683        let per_level = spent / PROBE_DEPTH;
4684        println!("checker: {spent} bytes for {PROBE_DEPTH} levels ({per_level} per level)");
4685        let needed = MAX_NESTING as usize * per_level * 2;
4686        assert!(
4687            needed < STACK_BYTES,
4688            "a ceiling of {MAX_NESTING} levels at {per_level} bytes each needs {needed} bytes \
4689             with the margin, against a declared STACK_BYTES of {STACK_BYTES} — raise the \
4690             declaration or lower the ceiling"
4691        );
4692    }
4693}
4694
4695/// `a and b` and `a or b`, lowered to the conditional they mean.
4696///
4697/// Beck's `and` and `or` were primitives over two `Bool`s, so both operands were evaluated before
4698/// either operator was applied. That is a difference nobody could see for three phases — every use
4699/// in the tree is pure, total and cheap — and [`53`](../../../../../docs/53-are-we-fast-yet-report.md)
4700/// §53.5 is where it became visible: a benchmark written the way its original is written searched
4701/// from positions its guard had already rejected.
4702///
4703/// The lowering happens **here** rather than in the evaluator, and that is the load-bearing choice.
4704/// Short-circuiting is a property of the language, not of one backend; put it in `interp.rs` and
4705/// the second backend has to remember it, which is the class of bug the backend seam exists to
4706/// prevent (`docs/19` §19.9). `CoreKind::If` already means "pick which computation runs", so no IR
4707/// node, no evaluator case and no runtime change was needed — the third feature running to be
4708/// added without one.
4709///
4710/// The *effect row* is deliberately unchanged. Both operands may run, so both are charged, exactly
4711/// as for any other `if`. What changes is how often they run, not what they are allowed to do.
4712///
4713/// A **bare reference** — `and` passed somewhere as a value — is untouched and still strict, and
4714/// that is not an oversight: a function value is handed arguments that have already been evaluated,
4715/// so no function value in any strict language short-circuits. `list_all` is what a fold over
4716/// conjunction wants.
4717fn short_circuit(p: Prim, args: &[Core], ty: Ty, span: Span) -> Option<Core> {
4718    let [lhs, rhs] = args else {
4719        return None;
4720    };
4721    let constant = |b: bool| Core::new(CoreKind::Const(Const::Bool(b)), ty.clone(), span);
4722    let (then, alt) = match p {
4723        Prim::And => (rhs.clone(), constant(false)),
4724        Prim::Or => (constant(true), rhs.clone()),
4725        _ => return None,
4726    };
4727    Some(Core::new(
4728        CoreKind::If {
4729            cond: Box::new(lhs.clone()),
4730            then: Box::new(then),
4731            alt: Box::new(alt),
4732        },
4733        ty,
4734        span,
4735    ))
4736}