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