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(¶ms, &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(¶m_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, ¶ms, 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, ¶ms, 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, ¶ms[0], lhs.span, &label);
2594 self.unify(&rhs.ty, ¶ms[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(¶ms)
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), ¶m_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, ¶m_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, ¶m_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}