Skip to main content

bynk_syntax/
ast.rs

1//! Abstract syntax tree types for Bynk v0 (spec §9.2).
2
3use crate::span::Span;
4
5/// An identifier with its source span.
6#[derive(Debug, Clone)]
7pub struct Ident {
8    pub name: String,
9    pub span: Span,
10}
11
12/// Comment trivia attached to a declaration or statement (v1.1 LSP spec
13/// §3.5). The parser collects line comments from the token stream and
14/// attaches them to nearby AST nodes so the formatter can re-emit them.
15///
16/// - `leading` holds comments that appear immediately above the node,
17///   ordered top-to-bottom: each a `--` line, or an orphaned `---` doc block
18///   (see [`Comment`]). Above a unit-level declaration it also records where a
19///   blank line separated them ([`Comment::Blank`]).
20/// - `trailing` holds a single comment that appears on the same source
21///   line as the node's final token (e.g. `expr  -- note`).
22#[derive(Debug, Clone, Default)]
23pub struct Trivia {
24    pub leading: Vec<Comment>,
25    pub trailing: Option<String>,
26}
27
28/// One entry of comment trivia.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum Comment {
31    /// A `--` line comment: the text after the marker, with its original
32    /// inline whitespace preserved.
33    Line(String),
34    /// #1756: a doc (a `---` block, or since #1888 a run of `--|` lines) that
35    /// attaches to no declaration (a blank line separates it from the next
36    /// one, or nothing follows it). The parser warns
37    /// `bynk.parse.orphan_doc_block` and keeps the block here, so the formatter
38    /// can print it where it was. Its content is normalised as an attached
39    /// doc's is.
40    OrphanDoc(Doc),
41    /// #1884: a blank line after a `--` comment, before the next comment or
42    /// the declaration. Recorded only where canonical style sets declarations
43    /// apart with blank lines: in the leading and closing comments of a
44    /// unit-level declaration (an item, a `uses`/`consumes`/`exports` clause,
45    /// the unit header), and of a service's sections and handlers or an agent's
46    /// invariants, transitions and handlers. There it keeps a comment that is
47    /// separated from the declaration below from reading as describing it.
48    /// Elsewhere (statements, record fields, capability ops, policy fields,
49    /// store fields) style has no blank lines, so the parser drops it. Several
50    /// blank lines are one entry.
51    Blank,
52}
53
54/// #1888: a documentation comment as written: its normalised text and the
55/// form it was written in. Every `documentation` field holds one. It derefs to
56/// its text, so a reader that only wants the Markdown treats it as a `str`;
57/// only the formatter reads [`Doc::form`], to print each doc in the form its
58/// author chose (DECISION F: it never converts between them).
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Doc {
61    /// The Markdown text: a `---` block's content with its common indent
62    /// stripped, or the `--|` lines' text joined with newlines.
63    pub text: String,
64    pub form: DocForm,
65}
66
67/// #1888: which of the two doc forms a [`Doc`] was written in.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
69pub enum DocForm {
70    /// A `---` fenced block (ADR 0188; matched-length fences since #1885).
71    #[default]
72    Block,
73    /// One or more consecutive `--|` lines.
74    Lines,
75}
76
77impl Doc {
78    pub fn new(text: impl Into<String>, form: DocForm) -> Self {
79        Self {
80            text: text.into(),
81            form,
82        }
83    }
84
85    pub fn as_str(&self) -> &str {
86        &self.text
87    }
88}
89
90impl std::ops::Deref for Doc {
91    type Target = str;
92    fn deref(&self) -> &str {
93        &self.text
94    }
95}
96
97/// A doc built from bare text is a `---` block, the form every doc had before
98/// #1888.
99impl From<String> for Doc {
100    fn from(text: String) -> Self {
101        Self::new(text, DocForm::Block)
102    }
103}
104
105impl From<&str> for Doc {
106    fn from(text: &str) -> Self {
107        Self::new(text, DocForm::Block)
108    }
109}
110
111impl std::fmt::Display for Doc {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        f.write_str(&self.text)
114    }
115}
116
117impl Trivia {
118    pub fn is_empty(&self) -> bool {
119        self.leading.is_empty() && self.trailing.is_none()
120    }
121}
122
123/// A whole parsed commons source file.
124///
125/// In v0.3 a commons may be split across multiple files in a directory; the
126/// resolver merges them into one logical commons. Each parsed AST instance
127/// represents the contribution from a single source file.
128#[derive(Debug, Clone)]
129pub struct Commons {
130    pub name: QualifiedName,
131    pub items: Vec<CommonsItem>,
132    /// `uses` clauses declared in this file.
133    pub uses: Vec<UsesDecl>,
134    /// Optional documentation block attached to the commons declaration.
135    pub documentation: Option<Doc>,
136    /// Surface form of the file: brace-delimited body or headerless fragment.
137    pub form: CommonsForm,
138    pub span: Span,
139    /// Trivia attached to the commons declaration itself — leading comments
140    /// before the `commons` keyword and a trailing comment after the header
141    /// or closing brace.
142    pub trivia: Trivia,
143    /// Comments appearing after the last item but before the file ends
144    /// (or the closing brace, for brace form). One entry per `--` line.
145    pub trailing_comments: Vec<Comment>,
146}
147
148/// The two surface forms in which a commons body may be parsed (v0.3 §3.1).
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum CommonsForm {
151    /// `commons name { ... }`
152    Brace,
153    /// `commons name` followed by top-level declarations to EOF.
154    Fragment,
155}
156
157/// A `uses other.commons` declaration (v0.3 §3.3).
158#[derive(Debug, Clone)]
159pub struct UsesDecl {
160    pub target: QualifiedName,
161    pub span: Span,
162    pub trivia: Trivia,
163}
164
165/// A whole parsed context source file (v0.4 §3.1).
166///
167/// Contexts are the architectural-layer declaration kind. Like commons, a
168/// context may be split across multiple files in a directory.
169#[derive(Debug, Clone)]
170pub struct Context {
171    pub name: QualifiedName,
172    pub items: Vec<CommonsItem>,
173    /// `uses` clauses declared in this file.
174    pub uses: Vec<UsesDecl>,
175    /// `consumes` clauses declared in this file.
176    pub consumes: Vec<ConsumesDecl>,
177    /// `exports` clauses declared in this file.
178    pub exports: Vec<ExportsDecl>,
179    /// Optional documentation block attached to the context declaration.
180    pub documentation: Option<Doc>,
181    /// Surface form of the file: brace-delimited body or headerless fragment.
182    pub form: CommonsForm,
183    pub span: Span,
184    /// Trivia attached to the context declaration itself — leading comments
185    /// before the `context` keyword.
186    pub trivia: Trivia,
187    /// Comments appearing after the last item but before the file ends
188    /// (or the closing brace, for brace form). One entry per `--` line.
189    pub trailing_comments: Vec<Comment>,
190}
191
192/// A `consumes other.context` declaration (v0.4 §3.2). May optionally carry
193/// an alias introduced by `consumes other.context as Alias` (v0.6 §3.1).
194#[derive(Debug, Clone)]
195pub struct ConsumesDecl {
196    pub target: QualifiedName,
197    pub alias: Option<Ident>,
198    /// v0.17: `consumes U { Cap, … }` — selected capabilities flattened into
199    /// the consumer's local capability namespace under their bare names (§3.3).
200    /// `None` for the whole-unit forms; `Some` (possibly empty) for the braced
201    /// form. Mutually exclusive with `alias`.
202    pub selected: Option<Vec<Ident>>,
203    pub span: Span,
204    pub trivia: Trivia,
205}
206
207/// An `exports visibility { names }` clause (v0.4 §3.3) or, v0.15, an
208/// `exports capability { names }` clause.
209#[derive(Debug, Clone)]
210pub struct ExportsDecl {
211    pub kind: ExportKind,
212    pub names: Vec<ExportName>,
213    pub span: Span,
214    pub trivia: Trivia,
215    /// #1797: comments before the closing `}`.
216    pub trailing_comments: Vec<Comment>,
217}
218
219/// One name in an `exports` list, with its comments (#1797).
220#[derive(Debug, Clone)]
221pub struct ExportName {
222    pub name: Ident,
223    /// #1797: comments above the name and at the end of its line. A comment
224    /// on the list's `{` line leads the first name.
225    pub trivia: Trivia,
226}
227
228/// What an `exports` clause exposes: types (with a visibility) or, v0.15,
229/// capabilities offered for cross-context consumption.
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231pub enum ExportKind {
232    /// `exports opaque { ... }` / `exports transparent { ... }` — type exports.
233    Type(Visibility),
234    /// `exports capability { ... }` — capabilities offered to consumers (v0.15).
235    Capability,
236}
237
238/// Visibility level for an exports clause (v0.4 §3.3).
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub enum Visibility {
241    /// Token-only outside the context: hold, pass, compare; no inspect, no construct.
242    Opaque,
243    /// Readable shape outside the context: inspect fields, match variants; no construct.
244    Transparent,
245}
246
247/// An `adapter qualified.name { … }` declaration (v0.17 §3.1). An adapter
248/// co-locates a capability contract with a non-Bynk binding: it may declare
249/// capabilities, the boundary types they reference, inline pure helper
250/// `type`/`fn` (and `uses`), external (bodiless) providers, `exports
251/// capability`, and exactly one `binding` clause. It may *not* declare
252/// services, agents, or bodied providers. Like commons/contexts it may be
253/// split across files in a directory.
254#[derive(Debug, Clone)]
255pub struct AdapterDecl {
256    pub name: QualifiedName,
257    pub items: Vec<CommonsItem>,
258    /// `uses` clauses declared in this file (pure-vocabulary mixin; allowed
259    /// because helpers cannot pierce containment — spec [DECISION B]).
260    pub uses: Vec<UsesDecl>,
261    /// `exports capability { … }` clauses (adapters export capabilities and
262    /// boundary types, never services).
263    pub exports: Vec<ExportsDecl>,
264    /// v0.18: `consumes U { Cap, … }` clauses — adapter-to-adapter capability
265    /// dependencies (spec §4.5, \[N\]). Braced form only; adapter targets only
266    /// (both enforced semantically, not in the parser).
267    pub consumes: Vec<ConsumesDecl>,
268    /// The `binding "<module>" requires { … }` clause, if present. Required
269    /// when the adapter declares any external provider (`bynk.adapter.no_binding`).
270    pub binding: Option<BindingDecl>,
271    pub documentation: Option<Doc>,
272    pub form: CommonsForm,
273    pub span: Span,
274    pub trivia: Trivia,
275    pub trailing_comments: Vec<Comment>,
276}
277
278/// A `binding "<module>" requires { "pkg": "range", … }` clause inside an
279/// adapter (v0.17 §3.5). `module` is the TypeScript module supplying the
280/// adapter's external provider symbols, resolved relative to the adapter's
281/// source file. `requires` declares npm dependencies folded into the
282/// generated `package.json`.
283#[derive(Debug, Clone)]
284pub struct BindingDecl {
285    /// The module path as written (the string-literal contents, no quotes).
286    pub module: String,
287    pub module_span: Span,
288    pub requires: Vec<RequiresDep>,
289    pub span: Span,
290    pub trivia: Trivia,
291}
292
293/// One `"pkg": "range"` entry in a binding's `requires { … }` map.
294#[derive(Debug, Clone)]
295pub struct RequiresDep {
296    pub package: String,
297    pub range: String,
298    pub span: Span,
299}
300
301/// Either a commons or a context — the two declaration kinds at the file
302/// level (v0.4 §3.1). v0.7 adds the test declaration kind; v0.17 the adapter.
303#[derive(Debug, Clone)]
304pub enum SourceUnit {
305    Commons(Commons),
306    Context(Context),
307    Suite(SuiteDecl),
308    /// v0.17: an `adapter` unit — the host boundary (capability contract +
309    /// external binding).
310    Adapter(AdapterDecl),
311}
312
313impl SourceUnit {
314    pub fn name(&self) -> &QualifiedName {
315        match self {
316            SourceUnit::Commons(c) => &c.name,
317            SourceUnit::Context(c) => &c.name,
318            SourceUnit::Suite(t) => &t.target,
319            SourceUnit::Adapter(a) => &a.name,
320        }
321    }
322
323    pub fn span(&self) -> Span {
324        match self {
325            SourceUnit::Commons(c) => c.span,
326            SourceUnit::Context(c) => c.span,
327            SourceUnit::Suite(t) => t.span,
328            SourceUnit::Adapter(a) => a.span,
329        }
330    }
331
332    /// The doc-block above this file's unit header, if it carries one. For a
333    /// `commons`/`context`/`adapter` this is the unit's *module doc* (#1885);
334    /// for a `suite` it is the suite's own doc.
335    pub fn documentation(&self) -> Option<&str> {
336        match self {
337            SourceUnit::Commons(c) => c.documentation.as_deref(),
338            SourceUnit::Context(c) => c.documentation.as_deref(),
339            SourceUnit::Suite(t) => t.documentation.as_deref(),
340            SourceUnit::Adapter(a) => a.documentation.as_deref(),
341        }
342    }
343
344    /// The doc-block above this file's unit header when it carries prose:
345    /// [`SourceUnit::documentation`], with an empty or all-whitespace block
346    /// treated as absent. This is what "a file carries a module doc" means
347    /// for the one-module-doc-per-unit rule and for the tooling that shows the
348    /// doc (#1885): an empty `---`/`---` pair documents nothing, so it neither
349    /// counts against the rule nor shadows a sibling file's real doc.
350    pub fn module_doc(&self) -> Option<&str> {
351        self.documentation().filter(|d| !d.trim().is_empty())
352    }
353
354    pub fn kind_name(&self) -> &'static str {
355        match self {
356            SourceUnit::Commons(_) => "commons",
357            SourceUnit::Context(_) => "context",
358            SourceUnit::Suite(_) => "suite",
359            SourceUnit::Adapter(_) => "adapter",
360        }
361    }
362}
363
364/// A `test <qualified-name> { ... }` declaration (v0.7 §3.1).
365///
366/// A test targets a commons or context by qualified name and bundles a set of
367/// test cases plus optional mock declarations. As with commons and contexts, a
368/// test may be split across multiple files (fragment form).
369#[derive(Debug, Clone)]
370pub struct SuiteDecl {
371    /// The targeted commons or context.
372    pub target: QualifiedName,
373    /// `uses` clauses brought in by this test fragment.
374    pub uses: Vec<UsesDecl>,
375    /// v0.118: suite-scoped `stub` clauses — per-seam provider overrides
376    /// applied to every case (a case-scoped `stub` takes precedence). Formerly
377    /// the punned `provides` stub; renamed to `stub` in the keyword-hygiene
378    /// batch (#548).
379    pub stubs: Vec<StubClause>,
380    /// The individual test cases.
381    pub cases: Vec<Case>,
382    /// v0.114: generative `property` blocks (testing track slice 2).
383    pub properties: Vec<PropertyDecl>,
384    /// v0.118: the suite-level tier default (`suite … as integration`). `None`
385    /// means the `unit` default; a `case`'s own tier overrides it. A `property`
386    /// ignores a suite tier (tiers are a `case`-only affordance).
387    pub tier: Option<TestTier>,
388    /// Surface form: brace-delimited body or headerless fragment.
389    pub form: CommonsForm,
390    /// Optional documentation block attached to the test declaration.
391    pub documentation: Option<Doc>,
392    pub span: Span,
393    pub trivia: Trivia,
394    pub trailing_comments: Vec<Comment>,
395}
396
397/// v0.118: the tier a `case` runs at (testing track slice 6, ADR 0153). One
398/// body promoted across the testing pyramid; `unit` is the default and elided.
399#[derive(Debug, Clone, Copy, PartialEq, Eq)]
400pub enum TestTier {
401    /// Collaborators stubbed (the default).
402    Unit,
403    /// Real collaborators within one context, no serialisation wire.
404    Integration,
405    /// Contexts wired across the real serialise → JSON → deserialise boundary.
406    System,
407}
408
409impl TestTier {
410    pub fn as_str(self) -> &'static str {
411        match self {
412            TestTier::Unit => "unit",
413            TestTier::Integration => "integration",
414            TestTier::System => "system",
415        }
416    }
417}
418
419/// v0.118: a per-seam provider override `stub Cap.method(<args>) returns <v>
420/// | fails` (testing track slice 6, ADR 0154; keyword `stub` since #548).
421/// Substitutes one capability method's provision under test; the right-hand
422/// side is a value or a fault, never a computed body.
423#[derive(Debug, Clone)]
424pub struct StubClause {
425    /// The capability being overridden (a consumed seam of the unit).
426    pub capability: Ident,
427    /// The overridden method.
428    pub method: Ident,
429    /// One argument pattern per parameter (`_` or a value the arg must equal).
430    pub args: Vec<ArgPattern>,
431    /// The provision: a value, a fault, or a per-call sequence.
432    pub rhs: StubRhs,
433    pub documentation: Option<Doc>,
434    pub span: Span,
435    pub trivia: Trivia,
436}
437
438/// v0.118: one argument pattern in a `stub` call pattern. Patterns for the
439/// same method are tried top-to-bottom, first match wins.
440#[derive(Debug, Clone)]
441pub enum ArgPattern {
442    /// `_` — matches any argument.
443    Any(Span),
444    /// A value the recorded argument must equal (a literal or pure value expr).
445    Value(Expr),
446}
447
448/// v0.118: the right-hand side of a `stub` clause.
449#[derive(Debug, Clone)]
450pub enum StubRhs {
451    /// `returns <value>` — a single success value, repeated for every call.
452    Returns(Expr),
453    /// `fails` — inject a capability fault (Principle 3).
454    Fails(Span),
455    /// `returns each [<outcome>, …]` — one outcome per call, in order; the last
456    /// outcome repeats once the sequence is exhausted (DECISION V).
457    ReturnsEach(Vec<SeqOutcome>, Span),
458}
459
460impl StubRhs {
461    pub fn span(&self) -> Span {
462        match self {
463            StubRhs::Returns(e) => e.span,
464            StubRhs::Fails(s) => *s,
465            StubRhs::ReturnsEach(_, s) => *s,
466        }
467    }
468}
469
470/// v0.118: one outcome in a sequenced (`returns each`) `stub`.
471#[derive(Debug, Clone)]
472pub enum SeqOutcome {
473    /// A success value.
474    Value(Expr),
475    /// A fault.
476    Fails(Span),
477}
478
479/// A `case "name" [as <tier>] { [stub …] body }` block inside a suite
480/// (v0.7 §3.3; v0.118 adds the tier clause and case-scoped stubs).
481#[derive(Debug, Clone)]
482pub struct Case {
483    /// The test name, taken from the string literal.
484    pub name: String,
485    /// The span of the string literal — used for diagnostics and runtime
486    /// failure reports.
487    pub name_span: Span,
488    /// v0.118: the case's own tier, if written (`as integration` / `as system`).
489    /// `None` means inherit the suite default (itself `unit` when unset).
490    pub tier: Option<TestTier>,
491    /// v0.118: case-scoped `stub` clauses (override the suite's, and the
492    /// tier default).
493    pub stubs: Vec<StubClause>,
494    pub body: Block,
495    pub documentation: Option<Doc>,
496    pub span: Span,
497    pub trivia: Trivia,
498}
499
500/// A `property "name" { for all <bindings> [where <pred>] { body } }` block
501/// inside a suite (v0.114, testing track slice 2, ADR 0149). The generative
502/// sibling of [`Case`]: the runner draws inhabitants of each binding's type from
503/// its refinement domain and evaluates the body's `expect`s over them.
504#[derive(Debug, Clone)]
505pub struct PropertyDecl {
506    /// The property name, taken from the string literal.
507    pub name: String,
508    /// The span of the string literal — used for diagnostics and reports.
509    pub name_span: Span,
510    /// The `for all` binder: the generated bindings, an optional `where` filter,
511    /// and the predicate body.
512    pub forall: ForAll,
513    pub documentation: Option<Doc>,
514    pub span: Span,
515    pub trivia: Trivia,
516}
517
518/// The `for all x: T, … [where <pred>] { … }` binder inside a [`PropertyDecl`].
519#[derive(Debug, Clone)]
520pub struct ForAll {
521    /// The generated bindings, `x: T` (one or more).
522    pub bindings: Vec<ForAllBinding>,
523    /// An optional `where <pred>` filter (a pure `Bool`) applied to generated
524    /// tuples before the body runs.
525    pub where_pred: Option<Expr>,
526    /// The body — one or more statements, typically `expect`s.
527    pub body: Block,
528    pub span: Span,
529}
530
531/// One `for all` binding: `name: T`, where the runner generates inhabitants of
532/// `T` from its refinements.
533#[derive(Debug, Clone)]
534pub struct ForAllBinding {
535    pub name: Ident,
536    pub type_ref: TypeRef,
537}
538
539/// A capability reference in a `given` clause (v0.15 §3.2). A bare name is a
540/// local capability (`given Cap`); a dotted name refers to a capability a
541/// consumed context provides (`given B.Cap` / `given Alias.Cap`).
542#[derive(Debug, Clone)]
543pub struct CapRef {
544    /// `None` for a local capability; `Some(prefix)` for a cross-context
545    /// reference where `prefix` is a consumed-context qualified name or alias.
546    pub context: Option<QualifiedName>,
547    /// The capability's simple name (also the local deps key).
548    pub name: Ident,
549    pub span: Span,
550}
551
552impl CapRef {
553    /// The local deps key / capability simple name (e.g. `Clock`).
554    pub fn key(&self) -> &str {
555        &self.name.name
556    }
557
558    /// True when this references a capability provided by a consumed context.
559    pub fn is_cross_context(&self) -> bool {
560        self.context.is_some()
561    }
562
563    /// The cross-context prefix (consumed-context qualified name or alias) as
564    /// a dotted string, if any.
565    pub fn prefix(&self) -> Option<String> {
566        self.context.as_ref().map(|q| q.joined())
567    }
568}
569
570/// A dotted name like `fitness.units`.
571#[derive(Debug, Clone)]
572pub struct QualifiedName {
573    pub parts: Vec<Ident>,
574    pub span: Span,
575}
576
577impl QualifiedName {
578    pub fn joined(&self) -> String {
579        self.parts
580            .iter()
581            .map(|p| p.name.as_str())
582            .collect::<Vec<_>>()
583            .join(".")
584    }
585}
586
587// Finding #31 shrank `Expr`/`ExprKind` enough that clippy's variance check
588// between this enum's smallest and largest variants (`Service`/`Actor` vs.
589// `Type`/`Fn`) now crosses its threshold — a pre-existing size profile made
590// newly visible, not something #31 itself is scoped to fix. Boxing
591// `ServiceDecl`/`ActorDecl` here is a separate, unscoped refactor (its own
592// blast radius across every `CommonsItem::Service`/`Actor` construction and
593// match site) left for a future finding.
594#[allow(clippy::large_enum_variant)]
595#[derive(Debug, Clone)]
596pub enum CommonsItem {
597    Type(TypeDecl),
598    Fn(FnDecl),
599    /// `capability Name { fn op(...) -> T ... }` (v0.5; contexts only).
600    Capability(CapabilityDecl),
601    /// `provides Cap = ProviderName { fn op(...) -> T { ... } ... }` (v0.5).
602    Provider(ProviderDecl),
603    /// `service Name { on call(...) -> T { ... } ... }` (v0.5).
604    Service(ServiceDecl),
605    /// `agent Name { key id: T; state { ... }; on call ... }` (v0.5).
606    Agent(AgentDecl),
607    /// `actor Name { auth = Scheme, identity = T }` (v0.45). A nominal boundary
608    /// contract consumed by a handler's `by` clause; not a runnable entity.
609    Actor(ActorDecl),
610    /// `messages <tag> @reference { "code" => "template" ... }` — a message
611    /// bundle for one locale. Commons-only (checker-enforced, not grammar);
612    /// legal syntactically wherever any `CommonsItem` is, per the existing
613    /// `Service`/`Agent`-in-`adapter` precedent.
614    Messages(MessagesDecl),
615    /// `event Name = { fields }` (Events track, slice 0, spine #936).
616    /// Context-only (checker-enforced, not grammar) — the mirror image of
617    /// `Messages`' commons-only restriction, same mechanism.
618    Event(EventDecl),
619    /// `asset name = "path"` (#1883, slice 1a). A static file the platform
620    /// serves beside the Worker. Context or commons ([DECISION G]); an
621    /// adapter is rejected by the checker, not the grammar, like `Event`.
622    Asset(AssetDecl),
623}
624
625impl CommonsItem {
626    /// The declaring identifier, when the item is named by one. `Messages` is
627    /// the sole `None`: its locale tag is a `LocaleTag` string literal
628    /// (`"pt-BR"`), not an identifier, and synthesising an `Ident` from it
629    /// would be a lie any identifier-shaped consumer (rename, go-to-def) would
630    /// eventually surface.
631    pub fn name(&self) -> Option<&Ident> {
632        match self {
633            CommonsItem::Type(t) => Some(&t.name),
634            CommonsItem::Fn(f) => Some(f.name.ident()),
635            CommonsItem::Capability(c) => Some(&c.name),
636            CommonsItem::Provider(p) => Some(&p.provider_name),
637            CommonsItem::Service(s) => Some(&s.name),
638            CommonsItem::Agent(a) => Some(&a.name),
639            CommonsItem::Actor(a) => Some(&a.name),
640            CommonsItem::Messages(_) => None,
641            CommonsItem::Event(e) => Some(&e.name),
642            CommonsItem::Asset(a) => Some(&a.name),
643        }
644    }
645
646    /// The whole declaration's span, from its first token to its last.
647    pub fn span(&self) -> Span {
648        match self {
649            CommonsItem::Type(t) => t.span,
650            CommonsItem::Fn(f) => f.span,
651            CommonsItem::Capability(c) => c.span,
652            CommonsItem::Provider(p) => p.span,
653            CommonsItem::Service(s) => s.span,
654            CommonsItem::Agent(a) => a.span,
655            CommonsItem::Actor(a) => a.span,
656            CommonsItem::Messages(m) => m.span,
657            CommonsItem::Event(e) => e.span,
658            CommonsItem::Asset(a) => a.span,
659        }
660    }
661}
662
663/// `asset name = "path"` (#1883, slice 1a): one static file, served by the
664/// platform at a content-hashed URL and never reaching the Worker, so public
665/// by construction. `name` is a value of the built-in type `Asset`
666/// ([DECISION H]), whose `.url` is that URL.
667///
668/// `path` is the literal's value with escapes applied, relative to the
669/// project root. The checker rejects a path that is absolute or contains a
670/// `..` segment (`bynk.asset.unsafe_path`) or names a file outside the
671/// project's `assets/` folder (`bynk.asset.outside_assets_dir`), and one
672/// whose file does not exist there (`bynk.asset.missing_file`).
673#[derive(Debug, Clone)]
674pub struct AssetDecl {
675    pub name: Ident,
676    pub path: String,
677    pub path_span: Span,
678    /// `at "/favicon.ico"` (#1883, slice 2, [DECISION C]): a fixed URL path
679    /// to serve the file at, for clients that fetch a file by name without
680    /// reading a page (`favicon.ico`, `robots.txt`). `None` serves it at a
681    /// content-hashed path under `/_assets/`. With its span, for diagnostics.
682    pub at: Option<(String, Span)>,
683    pub documentation: Option<Doc>,
684    pub span: Span,
685    pub trivia: Trivia,
686}
687
688/// One locale's message bundle (v0.222+): `messages "<tag>" @reference { ... }`.
689/// `tag` is a `LocaleTag` string literal (like an entry's `code`/`template`);
690/// its refinement (`bynk.locale.types`) is checked by `check_messages_bundles`,
691/// which reports `bynk.messages.invalid_locale_tag` for a tag the pattern
692/// rejects.
693#[derive(Debug, Clone)]
694pub struct MessagesDecl {
695    pub tag: String,
696    pub tag_span: Span,
697    /// Every `@`-annotation attached to this block. The parser stays
698    /// permissive (zero or more, same as `store` field annotations); cardinality
699    /// (exactly one `@reference` per bundle, counted across every `Messages`
700    /// item in the commons) is a checker concern, not a parse error.
701    pub annotations: Vec<Annotation>,
702    pub entries: Vec<MessageEntry>,
703    pub documentation: Option<Doc>,
704    pub span: Span,
705    pub trivia: Trivia,
706}
707
708/// One `"code" => "template"` entry inside a `messages` block. Both sides are
709/// plain string literals — a template's `{name}` placeholders are resolved by
710/// a compile-time string scan during lowering, not parsed as expressions.
711#[derive(Debug, Clone)]
712pub struct MessageEntry {
713    pub code: String,
714    pub code_span: Span,
715    pub template: String,
716    pub template_span: Span,
717    pub span: Span,
718    /// #1888: the doc above the entry, in either form.
719    pub documentation: Option<Doc>,
720}
721
722/// A capability declaration (v0.5 §3.3). Capabilities are interface-like
723/// contracts for external dependencies, used inside contexts. They may only
724/// appear inside a `context` declaration.
725#[derive(Debug, Clone)]
726pub struct CapabilityDecl {
727    pub name: Ident,
728    pub ops: Vec<CapabilityOp>,
729    pub documentation: Option<Doc>,
730    pub span: Span,
731    /// #1756: comments before the closing `}`, an orphaned doc block among
732    /// them, so the formatter keeps them.
733    pub trailing_comments: Vec<Comment>,
734    pub trivia: Trivia,
735}
736
737/// One operation in a capability (signature only; no body).
738#[derive(Debug, Clone)]
739pub struct CapabilityOp {
740    pub name: Ident,
741    /// #926: `[T, …]` type parameters on the op itself; empty for a
742    /// non-generic op. Resolved only from an explicit type argument at the
743    /// call site (`Cap.op[Some](…)`) — never inferred.
744    pub type_params: Vec<TypeParam>,
745    pub params: Vec<Param>,
746    pub return_type: TypeRef,
747    pub documentation: Option<Doc>,
748    pub span: Span,
749    pub trivia: Trivia,
750}
751
752/// A provider declaration (v0.5 §3.4). Supplies an implementation for a
753/// capability.
754#[derive(Debug, Clone)]
755pub struct ProviderDecl {
756    /// The capability being implemented.
757    pub capability: Ident,
758    /// The provider's identifier (used in tests/config to select impls).
759    pub provider_name: Ident,
760    /// v0.12: capabilities this provider depends on (`provides X = Impl given
761    /// Y, Z { … }`). The provider's operation bodies may use these. v0.15:
762    /// a dependency may be a cross-context capability (`given B.Cap`).
763    pub given: Vec<CapRef>,
764    pub ops: Vec<ProviderOp>,
765    /// v0.17: an *external* provider — `provides Cap = Name` with **no** brace
766    /// block — inside an adapter, supplied by the adapter's binding rather than
767    /// a Bynk body. When `true`, `ops` is empty and the emitter produces no
768    /// class. The absence of the brace block (not an empty one) is the signal.
769    pub external: bool,
770    pub documentation: Option<Doc>,
771    pub span: Span,
772    pub trivia: Trivia,
773}
774
775/// One operation in a provider (signature plus body).
776#[derive(Debug, Clone)]
777pub struct ProviderOp {
778    pub name: Ident,
779    pub params: Vec<Param>,
780    pub return_type: TypeRef,
781    pub body: Block,
782    pub span: Span,
783    pub trivia: Trivia,
784}
785
786/// A service declaration (v0.5 §3.5). Services are the boundary interface
787/// of a context.
788#[derive(Debug, Clone)]
789pub struct ServiceDecl {
790    pub name: Ident,
791    /// The protocol the service conforms to, from the `from <protocol>` header
792    /// clause (v0.44). `Call` when there is no clause.
793    pub protocol: ServiceProtocol,
794    /// The optional service-level `by` default (v0.155) — a `by <Actor>` clause on
795    /// the service header, `service Api from http by v: Visitor { … }`. Every
796    /// handler that omits its own `by` inherits this one (injected by the
797    /// normalization pass). `None` when absent — handlers then fall back to the
798    /// per-protocol default actor (HTTP/WebSocket have none, so `by` stays
799    /// mandatory there). The "public / bearer-authed" fact is usually a service
800    /// fact, so this removes the per-handler repetition.
801    pub default_by: Option<ByClause>,
802    /// The optional service-level `given` default (v0.155) — a `given C1, C2`
803    /// clause on the service header, following the `by` default. Every handler
804    /// that declares no `given` of its own inherits this list. Empty when absent.
805    pub default_given: Vec<CapRef>,
806    /// The optional cross-origin (CORS) policy (v0.131, ADR 0159) — a `cors { }`
807    /// section in the service body, only meaningful on a `from http` service.
808    /// `None` when absent (same-origin default, byte-for-byte unchanged output).
809    pub cors: Option<CorsPolicy>,
810    /// The optional security-headers policy (v0.141, ADR 0164) — a `security { }`
811    /// section in the service body, only meaningful on a `from http` service.
812    /// `None` when absent, but unlike `cors` the *absence* still stamps the safe
813    /// defaults (`nosniff` on) — the emitter synthesises a default policy for every
814    /// `from http` service, so `None` here means "defaults", not "no headers".
815    pub security: Option<SecurityPolicy>,
816    /// The optional request-body-size policy (v0.142, ADR 0165) — a `limits { }`
817    /// section in the service body, only meaningful on a `from http` service. It
818    /// declares a per-service `maxBody` ceiling (in bytes) for the service's
819    /// body-taking routes; a route may override it with `@limit(maxBody: …)`.
820    /// `None` when absent (no cap — byte-for-byte unchanged output, the opt-in
821    /// CORS posture, not the `security` default-on posture).
822    pub limits: Option<LimitsPolicy>,
823    pub handlers: Vec<Handler>,
824    pub documentation: Option<Doc>,
825    pub span: Span,
826    /// #1756: comments before the closing `}`, an orphaned doc block among
827    /// them, so the formatter keeps them.
828    pub trailing_comments: Vec<Comment>,
829    pub trivia: Trivia,
830}
831
832/// A cross-origin resource-sharing policy on a `from http` service (v0.131,
833/// ADR 0159): the `cors { }` section in the service body. Parsed leniently as a
834/// list of `name: value` fields (the grammar accepts any field name — an unknown
835/// one is a checker diagnostic, per the `@`-annotation precedent, ADR 0111), and
836/// interpreted through the typed accessors below.
837///
838/// `Access-Control-Allow-Methods` is deliberately **not** a field — it is derived
839/// from the service's routes at emit time (the routes already enumerate the
840/// methods; a restated list would drift). Likewise `Allow-Headers` defaults to
841/// `content-type` (+ `Authorization` when a Bearer route exists) and is only
842/// stored here when the author overrides it.
843#[derive(Debug, Clone)]
844pub struct CorsPolicy {
845    /// The `cors { }` fields as written, in source order. Field names are
846    /// validated against the closed set (`origins`/`headers`/`credentials`/
847    /// `maxAge`) by the checker, not the parser.
848    pub fields: Vec<CorsField>,
849    pub span: Span,
850    /// #1786: comments before the closing `}`, an orphaned doc block among
851    /// them.
852    pub trailing_comments: Vec<Comment>,
853    pub trivia: Trivia,
854}
855
856/// One `name: value` field inside a `cors { }` policy (v0.131).
857#[derive(Debug, Clone)]
858pub struct CorsField {
859    pub name: Ident,
860    pub value: Expr,
861    pub span: Span,
862    /// #1786: the field's own comments, above it and at the end of its line.
863    pub trivia: Trivia,
864}
865
866impl CorsPolicy {
867    /// The raw value expression for a field, by name (the last one wins if a
868    /// field is repeated — the checker flags the duplicate separately).
869    pub fn field(&self, name: &str) -> Option<&Expr> {
870        self.fields
871            .iter()
872            .rev()
873            .find(|f| f.name.name == name)
874            .map(|f| &f.value)
875    }
876
877    /// The allowed origins — the string literals of the `origins:` list. An
878    /// absent or malformed field yields an empty list (the checker has already
879    /// reported the shape error; the emitter fails closed on an empty list).
880    pub fn origins(&self) -> Vec<String> {
881        Self::str_list(self.field("origins")).unwrap_or_default()
882    }
883
884    /// `true` iff `origins` is exactly the wildcard `["*"]`.
885    pub fn is_wildcard(&self) -> bool {
886        let os = self.origins();
887        os.len() == 1 && os[0] == "*"
888    }
889
890    /// Whether credentialed requests are allowed (`credentials: true`); defaults
891    /// to `false` when the field is absent.
892    pub fn credentials(&self) -> bool {
893        matches!(
894            self.field("credentials").map(|e| &e.kind),
895            Some(ExprKind::BoolLit(true))
896        )
897    }
898
899    /// The explicit `Access-Control-Allow-Headers` override, if the author gave
900    /// a `headers:` list; `None` leaves the emitter to apply its smart default.
901    pub fn allow_headers(&self) -> Option<Vec<String>> {
902        self.field("headers").and_then(Self::str_list_of)
903    }
904
905    /// The `Access-Control-Max-Age` in whole seconds, if a `maxAge:` duration was
906    /// given; `None` leaves the header off (the browser default).
907    pub fn max_age_secs(&self) -> Option<i64> {
908        match self.field("maxAge").map(|e| &e.kind) {
909            Some(ExprKind::DurationLit { millis, .. }) => Some(millis / 1_000),
910            _ => None,
911        }
912    }
913
914    /// Interpret an expression as a list of string literals, if it is one.
915    fn str_list(expr: Option<&Expr>) -> Option<Vec<String>> {
916        expr.and_then(Self::str_list_of)
917    }
918
919    fn str_list_of(expr: &Expr) -> Option<Vec<String>> {
920        match &expr.kind {
921            ExprKind::ListLit(items) => items
922                .iter()
923                .map(|e| match &e.kind {
924                    ExprKind::StrLit(s) => Some(s.clone()),
925                    _ => None,
926                })
927                .collect(),
928            _ => None,
929        }
930    }
931}
932
933/// A security-headers policy on a `from http` service (v0.141, ADR 0164): the
934/// `security { }` section in the service body. Parsed leniently as a list of
935/// `name: value` fields (an unknown one is a checker diagnostic, per the CORS /
936/// `@`-annotation precedent) and interpreted through the typed accessors below.
937///
938/// The closed set is `nosniff` (a `Bool`, default `true` — stamps
939/// `X-Content-Type-Options: nosniff`) and `hsts` (a positive `Duration`, opt-in —
940/// stamps `Strict-Transport-Security: max-age=…`). Unlike `cors`, the *safe*
941/// header is on by default: a `from http` service with no `security { }` still
942/// stamps `nosniff`, because a security header you have to remember to switch on
943/// is the one you forget (ADR 0164 DECISION A).
944#[derive(Debug, Clone)]
945pub struct SecurityPolicy {
946    /// The `security { }` fields as written, in source order. Field names are
947    /// validated against the closed set (`hsts`/`nosniff`) by the checker, not
948    /// the parser.
949    pub fields: Vec<SecurityField>,
950    pub span: Span,
951    /// #1786: comments before the closing `}`, an orphaned doc block among
952    /// them.
953    pub trailing_comments: Vec<Comment>,
954    pub trivia: Trivia,
955}
956
957/// One `name: value` field inside a `security { }` policy (v0.141).
958#[derive(Debug, Clone)]
959pub struct SecurityField {
960    pub name: Ident,
961    pub value: Expr,
962    pub span: Span,
963    /// #1786: the field's own comments, above it and at the end of its line.
964    pub trivia: Trivia,
965}
966
967impl SecurityPolicy {
968    /// The raw value expression for a field, by name (the last one wins if a
969    /// field is repeated — the checker flags the duplicate separately).
970    pub fn field(&self, name: &str) -> Option<&Expr> {
971        self.fields
972            .iter()
973            .rev()
974            .find(|f| f.name.name == name)
975            .map(|f| &f.value)
976    }
977
978    /// Whether `X-Content-Type-Options: nosniff` is stamped. Defaults to `true`
979    /// (the safe default, ADR 0164 DECISION A); only an explicit `nosniff: false`
980    /// opts out. A malformed value has already been reported by the checker; it
981    /// falls back to the safe default here.
982    pub fn nosniff(&self) -> bool {
983        !matches!(
984            self.field("nosniff").map(|e| &e.kind),
985            Some(ExprKind::BoolLit(false))
986        )
987    }
988
989    /// The `Strict-Transport-Security` `max-age` in whole seconds, if the author
990    /// opted in with an `hsts:` duration; `None` leaves HSTS off (the default —
991    /// HSTS pins the browser to HTTPS and is a deliberate opt-in, DECISION A).
992    pub fn hsts_max_age_secs(&self) -> Option<i64> {
993        match self.field("hsts").map(|e| &e.kind) {
994            Some(ExprKind::DurationLit { millis, .. }) => Some(millis / 1_000),
995            _ => None,
996        }
997    }
998}
999
1000/// A request-body-size policy on a `from http` service (v0.142, ADR 0165): the
1001/// `limits { }` section in the service body. Parsed leniently as a list of
1002/// `name: value` fields (an unknown one is a checker diagnostic, per the CORS /
1003/// `security` / `@`-annotation precedent) and interpreted through the typed
1004/// accessor below.
1005///
1006/// The closed set is `maxBody` — a positive `Int` byte count (there is no byte
1007/// `Size` literal yet; a `1.mb`-style literal is a named follow-on, the
1008/// `Duration` playbook). Unlike `security`, this is opt-in: a service with no
1009/// `limits { }` (and no route `@limit`) has no cap and emits byte-for-byte
1010/// unchanged output (ADR 0165 DECISION E — the CORS posture).
1011#[derive(Debug, Clone)]
1012pub struct LimitsPolicy {
1013    /// The `limits { }` fields as written, in source order. Field names are
1014    /// validated against the closed set (`maxBody`) by the checker, not the
1015    /// parser.
1016    pub fields: Vec<LimitsField>,
1017    pub span: Span,
1018    /// #1786: comments before the closing `}`, an orphaned doc block among
1019    /// them.
1020    pub trailing_comments: Vec<Comment>,
1021    pub trivia: Trivia,
1022}
1023
1024/// One `name: value` field inside a `limits { }` policy (v0.142).
1025#[derive(Debug, Clone)]
1026pub struct LimitsField {
1027    pub name: Ident,
1028    pub value: Expr,
1029    pub span: Span,
1030    /// #1786: the field's own comments, above it and at the end of its line.
1031    pub trivia: Trivia,
1032}
1033
1034impl LimitsPolicy {
1035    /// The raw value expression for a field, by name (the last one wins if a
1036    /// field is repeated — the checker flags the duplicate separately).
1037    pub fn field(&self, name: &str) -> Option<&Expr> {
1038        self.fields
1039            .iter()
1040            .rev()
1041            .find(|f| f.name.name == name)
1042            .map(|f| &f.value)
1043    }
1044
1045    /// The service-wide maximum request-body size in bytes, if the author gave a
1046    /// positive `maxBody:` `Int` literal; `None` leaves the service without a
1047    /// default cap. A malformed or non-positive value has already been reported
1048    /// by the checker; it falls back to `None` here (no cap).
1049    pub fn max_body(&self) -> Option<i64> {
1050        match self.field("maxBody").map(|e| &e.kind) {
1051            Some(ExprKind::IntLit { value, .. }) if *value > 0 => Some(*value),
1052            _ => None,
1053        }
1054    }
1055}
1056
1057/// The protocol a service conforms to — declared on the header via
1058/// `from <protocol>` (v0.44). `Call` is the default (no `from` clause): a
1059/// contract-mediated internal-RPC surface, not a wire protocol. Multi-endpoint
1060/// protocols (`Http`, `Cron`) carry no binding — the endpoint lives on each
1061/// handler; single-binding `Queue` carries its queue name.
1062#[derive(Debug, Clone)]
1063pub enum ServiceProtocol {
1064    /// No `from` clause: the service holds `on call` handlers only.
1065    Call,
1066    /// `from http` — many routes; each handler is `on <Method>("route")`.
1067    Http,
1068    /// `from cron` — many schedules; each handler is `on schedule("expr")`.
1069    Cron,
1070    /// `from queue("name")` — one bound queue; handlers are `on message(...)`.
1071    Queue { name: String },
1072    /// `from websocket(in: ClientFrame, out: ServerFrame)` — a held WebSocket
1073    /// connection (v0.103, real-time track slice 3). `in_type` is the inbound
1074    /// frame type (client→server, decoded and routed as typed agent messages);
1075    /// `out_type` is the server→client frame type the held `Connection[out_type]`
1076    /// carries. The service holds exactly one `on open` handler (edge auth via
1077    /// `by`, then transfer of the connection to an agent).
1078    WebSocket { in_type: TypeRef, out_type: TypeRef },
1079    /// `from Events(E)` or `from Events(E { field: value, .. })`, optionally
1080    /// followed by `via schema(N)` — a subscriber to event type `E`,
1081    /// optionally filtered by a structural payload pattern (Events track,
1082    /// slice 0 spine #936; the pattern is slice 1) and/or the envelope's
1083    /// `schemaVersion` (slice 4). `Events`, capitalised, is matched as plain
1084    /// `Ident` text the same way `websocket` is — it names the `Events`
1085    /// capability directly (every first-party capability is already an
1086    /// unreserved PascalCase identifier), not a built-in type name, so no
1087    /// lexer reservation. `pattern` and `schema_dispatch` are independent:
1088    /// a service may carry either, both, or neither.
1089    Events {
1090        event_type: TypeRef,
1091        pattern: Option<EventPattern>,
1092        schema_dispatch: Option<SchemaDispatch>,
1093    },
1094}
1095
1096/// An agent declaration (v0.5 §3.6). Agents are state-bearing entities
1097/// with their own handlers.
1098#[derive(Debug, Clone)]
1099pub struct AgentDecl {
1100    pub name: Ident,
1101    /// `key id: Type` — the identifier-typed value identifying instances.
1102    pub key_name: Ident,
1103    pub key_type: TypeRef,
1104    /// #1788: comments above the `key` line and at its end. A comment on the
1105    /// agent's `{` line leads the key too.
1106    pub key_trivia: Trivia,
1107    /// `store` fields (v0.81, storage track) — each an access-pattern slot of a
1108    /// declared storage kind (`Cell`/`Map`/…). The successor to the removed
1109    /// `state { }` record (ADR 0108); every agent declares its state this way.
1110    pub store_fields: Vec<StoreField>,
1111    /// Invariants (v0.80 §14) — universally-quantified predicates over the
1112    /// agent's `store` fields. The phase sits between the fields and the
1113    /// handlers; each is checked against the state staged by a handler's writes
1114    /// before it commits.
1115    pub invariants: Vec<Invariant>,
1116    /// Step invariants (v0.116 §, testing track slice 4) — named predicates over
1117    /// the pre-/post-commit state *pair* (`old`/`new`), checked at the commit
1118    /// boundary beside [`invariants`], from the second commit onward. Widen the
1119    /// invariant subject from a snapshot to a step (ADR 0144 — one predicate
1120    /// surface).
1121    ///
1122    /// [`invariants`]: AgentDecl::invariants
1123    pub transitions: Vec<Transition>,
1124    pub handlers: Vec<Handler>,
1125    pub documentation: Option<Doc>,
1126    pub span: Span,
1127    /// #1756: comments before the closing `}`, an orphaned doc block among
1128    /// them, so the formatter keeps them.
1129    pub trailing_comments: Vec<Comment>,
1130    pub trivia: Trivia,
1131}
1132
1133/// A `store` field (v0.81, storage track). Each is an access-pattern slot of a
1134/// declared storage kind: `store <name>: <Kind>[…] [@annotations] [= <init>]`.
1135/// The kind and its element type are carried as an ordinary [`TypeRef`]
1136/// (`Cell[Int]`, `Map[K, V]`); the checker restricts which heads are storage
1137/// kinds. Access-pattern annotations (`@indexed`, …) parse into [`annotations`]
1138/// (v0.85, ADR 0111); the checker validates them against the closed registry.
1139///
1140/// [`annotations`]: StoreField::annotations
1141#[derive(Debug, Clone)]
1142pub struct StoreField {
1143    pub name: Ident,
1144    /// The storage kind and its element type(s): `Cell[Int]`, `Map[K, V]`. A
1145    /// dedicated [`StoreKind`] rather than a [`TypeRef`] — storage kinds are not
1146    /// value types, and the checker dispatches kind-aware operations on the head.
1147    pub kind: StoreKind,
1148    /// Storage annotations on the field (v0.85, ADR 0111): `@ttl(5.minutes)`,
1149    /// `@indexed(by: orderId)`. Parsed in declaration order (after the kind,
1150    /// before the initialiser); the checker validates names against the closed
1151    /// registry and gates each to the slice that implements it.
1152    pub annotations: Vec<Annotation>,
1153    /// The fresh-key initial value (`= expr`), if given — same disposition as a
1154    /// `state` field's initialiser (ADRs 0003/0004 carry forward).
1155    pub init: Option<Expr>,
1156    pub documentation: Option<Doc>,
1157    pub span: Span,
1158    pub trivia: Trivia,
1159}
1160
1161/// A storage annotation on a `store` field (v0.85, storage track; ADR 0111):
1162/// `@<name>(<args>)`. The `name` is matched against the closed registry
1163/// (`@indexed`/`@ttl`/`@retain`/`@bounded`) by the checker; the grammar accepts
1164/// any identifier so an unknown name is a checker diagnostic, not a parse error.
1165/// Arguments are compile-time metadata, restricted to literals (and the `by:`
1166/// field-name labels of `@indexed`) by the checker per ADR 0111 D4.
1167#[derive(Debug, Clone)]
1168pub struct Annotation {
1169    pub name: Ident,
1170    pub args: Vec<AnnotationArg>,
1171    pub span: Span,
1172}
1173
1174/// A single annotation argument (v0.85; ADR 0111): an optional `label:` followed
1175/// by a value expression — `by: orderId` (labelled) or `5.minutes` (positional).
1176/// The value is parsed as an ordinary [`Expr`] so the duration-literal form
1177/// (`5.minutes`, landing with the `Duration` slice) needs no special grammar;
1178/// the checker restricts it to a literal where the annotation is functional.
1179#[derive(Debug, Clone)]
1180pub struct AnnotationArg {
1181    pub label: Option<Ident>,
1182    pub value: Expr,
1183    pub span: Span,
1184}
1185
1186/// A storage kind applied to its element type(s) (v0.81): `Cell[Int]`,
1187/// `Map[ReservationId, Reservation]`. The `head` is the kind name (`Cell`,
1188/// `Map`, `Set`, `Log`, `Queue`, `Cache`); the checker validates it against the
1189/// closed catalogue. Element types are ordinary [`TypeRef`]s. Refined element
1190/// types (`Cell[Int where NonNegative]`) ride a later slice (parse_type_ref does
1191/// not yet accept an inline refinement in type-argument position).
1192#[derive(Debug, Clone)]
1193pub struct StoreKind {
1194    pub head: Ident,
1195    pub args: Vec<TypeRef>,
1196    pub span: Span,
1197}
1198
1199/// An agent invariant (v0.80 §14). A named predicate over the agent's state
1200/// fields that must hold of every committed state; a commit that would violate
1201/// it faults (`InvariantViolation`) before the state is persisted. The
1202/// predicate references state fields by bare name, mirroring the design-notes
1203/// worked examples (`status == Paid implies paymentRef.isSome()`).
1204#[derive(Debug, Clone)]
1205pub struct Invariant {
1206    pub name: Ident,
1207    /// The predicate expression — an ordinary `Bool`-typed expression over the
1208    /// state fields, plus `implies` and `is`. The parsed-predicate-on-a-
1209    /// declaration shape mirrors [`ActorRefinement::predicate`].
1210    pub predicate: Expr,
1211    pub documentation: Option<Doc>,
1212    pub span: Span,
1213    pub trivia: Trivia,
1214}
1215
1216/// An agent step invariant (v0.116 §, testing track slice 4). A named predicate
1217/// over the *pair* of committed states — the pre-commit `old` and the proposed
1218/// `new`, each the agent's state record — that must hold of every state move; a
1219/// commit that would violate it faults (`InvariantViolation`) before the state is
1220/// persisted, exactly as a snapshot [`Invariant`] does. Widens the invariant
1221/// subject from a snapshot to a step (ADR 0144 — one predicate surface); the
1222/// predicate reuses the invariant surface (`implies`/`is`/pure methods) with
1223/// `old`/`new` bound contextually (`old.status is Paid implies new.status is
1224/// Paid`).
1225#[derive(Debug, Clone)]
1226pub struct Transition {
1227    pub name: Ident,
1228    /// The predicate expression — an ordinary `Bool`-typed expression over the
1229    /// `old` and `new` state records, with `implies`/`is` and pure methods,
1230    /// mirroring [`Invariant`].
1231    pub predicate: Expr,
1232    pub documentation: Option<Doc>,
1233    pub span: Span,
1234    pub trivia: Trivia,
1235}
1236
1237/// A function contract clause (v0.115 §, testing track slice 3). A named
1238/// predicate on a `fn` signature — a `requires` (precondition) or `ensures`
1239/// (postcondition). A contract is the invariant predicate attached to a
1240/// function (ADR 0144 — one predicate surface): the predicate is a pure `Bool`
1241/// expression over the parameters (`requires`) or the parameters plus `result`
1242/// (`ensures`), with `implies`/`is` and pure methods, mirroring [`Invariant`].
1243/// The name rides the failure report and the redundant-test dedup.
1244#[derive(Debug, Clone)]
1245pub struct Contract {
1246    pub name: Ident,
1247    /// The predicate expression — an ordinary `Bool`-typed expression over the
1248    /// parameters (and, for an `ensures`, the contextual `result` binding).
1249    pub predicate: Expr,
1250    pub span: Span,
1251}
1252
1253/// An actor declaration (v0.45 §3.7). An actor is a nominal *contract type*
1254/// describing an external party at a boundary — not a runnable entity. A
1255/// handler consumes an actor on its `by` clause; the boundary verifies the
1256/// declared `auth` scheme and mints a sealed identity (`name.identity`).
1257#[derive(Debug, Clone)]
1258pub struct ActorDecl {
1259    pub name: Ident,
1260    /// The authentication scheme from `auth = <Scheme>`, stored as the raw
1261    /// identifier. The checker classifies it: `None`/`Internal`/`Bearer` are
1262    /// admitted; `Signature` is reserved-and-rejected
1263    /// (`bynk.actor.scheme_unsupported`); anything else is
1264    /// `bynk.actor.unknown_scheme`. `None` for the refinement form.
1265    pub auth: Option<Ident>,
1266    /// The scheme's keyed config from `auth = Scheme(key = value, …)` (v0.47
1267    /// `Bearer(secret = "…")`; v0.51 generalised for `Signature(secret, header,
1268    /// timestamp?, tolerance?)`). Empty for schemes/forms with no config. The
1269    /// checker validates which keys each scheme requires/allows.
1270    pub auth_config: Vec<SchemeArg>,
1271    /// The optional identity type from `, identity = <T>`. Absent ⇒ the
1272    /// scheme default (`()` for `None`; a sealed `CallerId` for the `Internal`
1273    /// `on call` channel, `()` for other `Internal` channels).
1274    pub identity: Option<TypeRef>,
1275    /// The refinement form `actor Admin = Base where <predicate>` — narrows a
1276    /// base actor by an authorisation claim (ADR 0091). The predicate is parsed
1277    /// as a full expression; a static-semantics rule restricts it to the closed
1278    /// actor-claim catalogue (`hasClaim`/`claimEquals` over a `Bearer` base;
1279    /// `bynk.actor.refinement_predicate_unsupported` / `…_base_unsupported`).
1280    pub refinement: Option<ActorRefinement>,
1281    pub documentation: Option<Doc>,
1282    pub span: Span,
1283    pub trivia: Trivia,
1284    /// #1797: comments above the `auth` entry and at the end of its line. A
1285    /// comment on the actor's `{` line leads `auth` too. Empty for the
1286    /// refinement form.
1287    pub auth_trivia: Trivia,
1288    /// #1797: comments above the `identity` entry and at the end of its line.
1289    /// Empty when there is no `identity`.
1290    pub identity_trivia: Trivia,
1291    /// #1797: comments before the actor body's closing `}`.
1292    pub trailing_comments: Vec<Comment>,
1293}
1294
1295impl ActorDecl {
1296    /// The value of a scheme config arg by key, if present (e.g. `secret`,
1297    /// `header`).
1298    pub fn scheme_arg(&self, key: &str) -> Option<&SchemeArg> {
1299        self.auth_config.iter().find(|a| a.key.name == key)
1300    }
1301}
1302
1303/// One `key = value` argument in a scheme config (`Scheme(key = value, …)`).
1304#[derive(Debug, Clone)]
1305pub struct SchemeArg {
1306    pub key: Ident,
1307    pub value: SchemeArgValue,
1308    /// Span of the value, for diagnostics.
1309    pub span: Span,
1310}
1311
1312/// A scheme config arg value — a string literal or an integer.
1313#[derive(Debug, Clone)]
1314pub enum SchemeArgValue {
1315    Str(String),
1316    Int(i64),
1317}
1318
1319impl SchemeArgValue {
1320    pub fn as_str(&self) -> Option<&str> {
1321        match self {
1322            SchemeArgValue::Str(s) => Some(s),
1323            SchemeArgValue::Int(_) => None,
1324        }
1325    }
1326    pub fn as_int(&self) -> Option<i64> {
1327        match self {
1328            SchemeArgValue::Int(n) => Some(*n),
1329            SchemeArgValue::Str(_) => None,
1330        }
1331    }
1332}
1333
1334/// The reserved refinement form `actor Admin = User where <predicate>` (Q3).
1335/// Parsed in Foundations so the grammar is fixed; admission is a later slice.
1336#[derive(Debug, Clone)]
1337pub struct ActorRefinement {
1338    /// The base actor being refined.
1339    pub base: Ident,
1340    /// The `where` predicate. Parsed but not yet checked.
1341    pub predicate: Expr,
1342    pub span: Span,
1343}
1344
1345/// The `by (<binder>:)? <Actor>` clause on a handler (v0.45; binder optional in
1346/// v0.50). Names the actor contract the handler consumes; when a `binder` is
1347/// given, the verified identity binds to it and is read as `binder.identity`.
1348/// Omitting the binder (`by <Actor>`) declares-and-verifies the contract without
1349/// capturing the identity — for anonymous or verify-and-discard handlers. Sits
1350/// after the protocol config and before the parameters.
1351#[derive(Debug, Clone)]
1352pub struct ByClause {
1353    /// The identity binder, if the handler consumes the identity. `None` for the
1354    /// binder-less `by <Actor>` form. Required when `actors` names more than one
1355    /// (a sum is resolved by matching on the bound actor).
1356    pub binder: Option<Ident>,
1357    /// The actor contract(s) referenced — each a local actor decl or a prelude
1358    /// actor. A single name is the ordinary single-actor handler; more than one
1359    /// (`by who: A | B`, v0.52) is an **ordered sum of peer actors** resolved
1360    /// first-wins, the body matching on the resolved actor. Always non-empty.
1361    pub actors: Vec<Ident>,
1362    pub span: Span,
1363}
1364
1365impl ByClause {
1366    /// The first (and, for a single-actor handler, only) actor contract named.
1367    pub fn primary(&self) -> &Ident {
1368        &self.actors[0]
1369    }
1370    /// Whether this `by` clause names an ordered sum of peer actors (`A | B`).
1371    pub fn is_sum(&self) -> bool {
1372        self.actors.len() > 1
1373    }
1374}
1375
1376/// v0.182 (testing-the-boundary Slice A, #664): a call-site actor clause on a
1377/// test-body `let x <- <service address> by <Actor>(<identity>)`. Distinct from
1378/// [`ByClause`] (the handler/header form): the *declaration* names which actor
1379/// may call and binds the verified identity, whereas the *call site* names the
1380/// actor the case is acting as and supplies the identity value. A unit-identity
1381/// actor (`Visitor`, and cron/queue's internal actors) carries no `identity`.
1382#[derive(Debug, Clone)]
1383pub struct CallSiteActor {
1384    /// The actor the case acts as — a local actor decl or a prelude actor.
1385    pub actor: Ident,
1386    /// The supplied identity value (`"bob"` in `by User("bob")`), or `None` for a
1387    /// unit-identity actor written `by Visitor` with no argument.
1388    pub identity: Option<Box<Expr>>,
1389    pub span: Span,
1390}
1391
1392/// A handler block — `on call(args) -> T given C1, C2 { body }`.
1393/// Used by both services and agents.
1394#[derive(Debug, Clone)]
1395pub struct Handler {
1396    pub kind: HandlerKind,
1397    /// Handler-position annotations (v0.140, ADR 0163): `@cache(maxAge: 5.minutes)`
1398    /// written immediately before `on <METHOD>(…)`. Reuses the [`Annotation`] AST
1399    /// shared with `store` fields (ADR 0111); the grammar accepts any `@name(args)`
1400    /// so an unknown name is a project-validation diagnostic, not a parse error. The
1401    /// first handler-position annotation surface — empty for every handler that
1402    /// carries none.
1403    pub annotations: Vec<Annotation>,
1404    /// For agent handlers, the method-style handler name (e.g.
1405    /// `on call addItem(...)`). For service handlers, this is None (just
1406    /// `on call(...)`).
1407    pub method_name: Option<Ident>,
1408    /// The `by <binder>: <Actor>` clause (v0.45), if present. Service handlers
1409    /// only; an absent clause inherits the protocol's default actor.
1410    pub by_clause: Option<ByClause>,
1411    pub params: Vec<Param>,
1412    pub return_type: TypeRef,
1413    pub given: Vec<CapRef>,
1414    pub body: Block,
1415    pub documentation: Option<Doc>,
1416    pub span: Span,
1417    pub trivia: Trivia,
1418}
1419
1420/// `Hash` (P8.5, #1516): a `HandlerKind` value is a body-free, span-free
1421/// discriminant, added so a `DefId`-keyed handler identity could carry it
1422/// (`bynk-check`'s former `queries::HandlerDefId`, deleted by #1537 — kept
1423/// here because the derive is harmless, and a rebuild against ADR 0417's
1424/// shape would need it again). Purely additive; no existing
1425/// caller matches on hashing behaviour.
1426#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1427pub enum HandlerKind {
1428    /// `on call(...)` — typed RPC (the only kind in v0.5).
1429    Call,
1430    /// `on http METHOD "path"` — external-facing HTTP route (v0.9).
1431    Http { method: HttpMethod, path: String },
1432    /// `on cron "expr"` — scheduled task; `expr` is a 5-field cron
1433    /// expression (v0.10a).
1434    Cron { expr: String },
1435    /// `on message(m: T)` — a message off the service's bound queue. The queue
1436    /// binding lives on the service's `ServiceProtocol::Queue` (v0.44).
1437    Message,
1438    /// `on open ...` — the WebSocket upgrade handler (v0.103, real-time track
1439    /// slice 3). Exactly one per `from websocket` service; carries a mandatory
1440    /// `by` clause (edge auth) and receives a fresh owned `Connection[out]`.
1441    Open,
1442    /// `on close ...` — the WebSocket close handler (v0.106, real-time track slice
1443    /// 3b-iii). Optional, ≤1 per `from websocket` service; runs when the socket
1444    /// closes. Like `on open`, edge-authenticated (`by`), with the identity/params
1445    /// recovered from the socket attachment (set at `on open`). (A `from websocket`
1446    /// `on message` reuses [`HandlerKind::Message`], disambiguated by the protocol.)
1447    Close,
1448    /// `on event(e: E)` — one emission of a `from Events(E)` service's
1449    /// subscribed event type (Events track, slice 0, spine #936). No
1450    /// envelope parameter yet (slice 2). `event`, like `message`/`open`/
1451    /// `close`/`schedule`, is matched by plain ident text at the fixed
1452    /// position right after `on`, with no lexer reservation — an ordinary
1453    /// identifier everywhere else in the grammar.
1454    Event,
1455}
1456
1457/// HTTP methods supported by `on http` handlers (v0.9).
1458#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1459pub enum HttpMethod {
1460    Get,
1461    Post,
1462    Put,
1463    Patch,
1464    Delete,
1465}
1466
1467impl HttpMethod {
1468    pub fn as_str(self) -> &'static str {
1469        match self {
1470            HttpMethod::Get => "GET",
1471            HttpMethod::Post => "POST",
1472            HttpMethod::Put => "PUT",
1473            HttpMethod::Patch => "PATCH",
1474            HttpMethod::Delete => "DELETE",
1475        }
1476    }
1477
1478    pub fn from_ident(s: &str) -> Option<HttpMethod> {
1479        match s {
1480            "GET" => Some(HttpMethod::Get),
1481            "POST" => Some(HttpMethod::Post),
1482            "PUT" => Some(HttpMethod::Put),
1483            "PATCH" => Some(HttpMethod::Patch),
1484            "DELETE" => Some(HttpMethod::Delete),
1485            _ => None,
1486        }
1487    }
1488
1489    /// True if this method conventionally has no request body.
1490    pub fn forbids_body(self) -> bool {
1491        matches!(self, HttpMethod::Get | HttpMethod::Delete)
1492    }
1493}
1494
1495/// Payload shape of an `HttpResult[T]` variant (v0.9 §3.3).
1496#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1497pub enum HttpVariantPayload {
1498    /// No payload (e.g. `NoContent`, `Unauthorized`).
1499    None,
1500    /// Carries a value of the `HttpResult` type parameter `T`.
1501    Value,
1502    /// Carries a `String` message (e.g. `BadRequest`, `Conflict`).
1503    Message,
1504    /// Carries a `String` target URL, emitted as a `Location` header — the
1505    /// redirect variants (`Found`, `SeeOther`, `PermanentRedirect`, …).
1506    Location,
1507    /// Carries a `Stream[String]`, emitted as an SSE (`text/event-stream`)
1508    /// streaming body — the `Streaming` (200) variant (v0.101, real-time track
1509    /// slice 1).
1510    Streamed,
1511    /// Carries `(body: Bytes, contentType: String)` — the author-owned raw body
1512    /// written straight into the response with the declared `content-type` and
1513    /// **no codec** (the typed-wire guarantee is deliberately off). The `Raw`
1514    /// (200) variant (v0.111); the first two-argument payload shape.
1515    Raw,
1516}
1517
1518/// One variant of the built-in `HttpResult[T]` sum (v0.9 §3.3).
1519#[derive(Debug, Clone, Copy)]
1520pub struct HttpVariant {
1521    pub name: &'static str,
1522    pub payload: HttpVariantPayload,
1523    pub status: u16,
1524}
1525
1526/// All `HttpResult[T]` variants, in declaration order (ascending status). The
1527/// vocabulary tracks the common, modern HTTP status codes (RFC 9110): success
1528/// and created/accepted (`Value`), redirects carrying a `Location` URL, and
1529/// the client/server failures that handlers routinely return (`Message` when
1530/// an explanation helps the caller, `None` for self-describing statuses).
1531pub const HTTP_VARIANTS: &[HttpVariant] = &[
1532    // ── 2xx success ──────────────────────────────────────────────────────
1533    HttpVariant {
1534        name: "Ok",
1535        payload: HttpVariantPayload::Value,
1536        status: 200,
1537    },
1538    // v0.101 (real-time track slice 1): a 200 whose body is a streamed
1539    // `Stream[String]`, SSE-framed. Status precedes the body, so streaming is
1540    // 200-only — pre-stream failures are ordinary variants returned instead.
1541    HttpVariant {
1542        name: "Streaming",
1543        payload: HttpVariantPayload::Streamed,
1544        status: 200,
1545    },
1546    // v0.111: a 200 whose body is an author-owned `Bytes` written straight into
1547    // the response with the declared `content-type` — no codec runs. 200-only,
1548    // like `Streaming`: it serves service-tier raw bodies (`robots.txt`,
1549    // `sitemap.xml`, feeds, a QR PNG), not custom-status error pages.
1550    HttpVariant {
1551        name: "Raw",
1552        payload: HttpVariantPayload::Raw,
1553        status: 200,
1554    },
1555    HttpVariant {
1556        name: "Created",
1557        payload: HttpVariantPayload::Value,
1558        status: 201,
1559    },
1560    HttpVariant {
1561        name: "Accepted",
1562        payload: HttpVariantPayload::Value,
1563        status: 202,
1564    },
1565    HttpVariant {
1566        name: "NoContent",
1567        payload: HttpVariantPayload::None,
1568        status: 204,
1569    },
1570    // ── 3xx redirection (carry a `Location` URL) ─────────────────────────
1571    HttpVariant {
1572        name: "MovedPermanently",
1573        payload: HttpVariantPayload::Location,
1574        status: 301,
1575    },
1576    HttpVariant {
1577        name: "Found",
1578        payload: HttpVariantPayload::Location,
1579        status: 302,
1580    },
1581    HttpVariant {
1582        name: "SeeOther",
1583        payload: HttpVariantPayload::Location,
1584        status: 303,
1585    },
1586    HttpVariant {
1587        name: "TemporaryRedirect",
1588        payload: HttpVariantPayload::Location,
1589        status: 307,
1590    },
1591    HttpVariant {
1592        name: "PermanentRedirect",
1593        payload: HttpVariantPayload::Location,
1594        status: 308,
1595    },
1596    // ── 4xx client error ─────────────────────────────────────────────────
1597    HttpVariant {
1598        name: "BadRequest",
1599        payload: HttpVariantPayload::Message,
1600        status: 400,
1601    },
1602    HttpVariant {
1603        name: "Unauthorized",
1604        payload: HttpVariantPayload::None,
1605        status: 401,
1606    },
1607    HttpVariant {
1608        name: "Forbidden",
1609        payload: HttpVariantPayload::None,
1610        status: 403,
1611    },
1612    HttpVariant {
1613        name: "NotFound",
1614        payload: HttpVariantPayload::None,
1615        status: 404,
1616    },
1617    HttpVariant {
1618        name: "MethodNotAllowed",
1619        payload: HttpVariantPayload::None,
1620        status: 405,
1621    },
1622    HttpVariant {
1623        name: "NotAcceptable",
1624        payload: HttpVariantPayload::None,
1625        status: 406,
1626    },
1627    HttpVariant {
1628        name: "RequestTimeout",
1629        payload: HttpVariantPayload::None,
1630        status: 408,
1631    },
1632    HttpVariant {
1633        name: "Conflict",
1634        payload: HttpVariantPayload::Message,
1635        status: 409,
1636    },
1637    HttpVariant {
1638        name: "Gone",
1639        payload: HttpVariantPayload::None,
1640        status: 410,
1641    },
1642    HttpVariant {
1643        name: "LengthRequired",
1644        payload: HttpVariantPayload::None,
1645        status: 411,
1646    },
1647    HttpVariant {
1648        name: "PayloadTooLarge",
1649        payload: HttpVariantPayload::Message,
1650        status: 413,
1651    },
1652    HttpVariant {
1653        name: "UnsupportedMediaType",
1654        payload: HttpVariantPayload::Message,
1655        status: 415,
1656    },
1657    HttpVariant {
1658        name: "UnprocessableEntity",
1659        payload: HttpVariantPayload::Message,
1660        status: 422,
1661    },
1662    HttpVariant {
1663        name: "TooManyRequests",
1664        payload: HttpVariantPayload::Message,
1665        status: 429,
1666    },
1667    HttpVariant {
1668        name: "UnavailableForLegalReasons",
1669        payload: HttpVariantPayload::Message,
1670        status: 451,
1671    },
1672    // ── 5xx server error ─────────────────────────────────────────────────
1673    HttpVariant {
1674        name: "ServerError",
1675        payload: HttpVariantPayload::Message,
1676        status: 500,
1677    },
1678    HttpVariant {
1679        name: "NotImplemented",
1680        payload: HttpVariantPayload::Message,
1681        status: 501,
1682    },
1683    HttpVariant {
1684        name: "BadGateway",
1685        payload: HttpVariantPayload::Message,
1686        status: 502,
1687    },
1688    HttpVariant {
1689        name: "ServiceUnavailable",
1690        payload: HttpVariantPayload::Message,
1691        status: 503,
1692    },
1693    HttpVariant {
1694        name: "GatewayTimeout",
1695        payload: HttpVariantPayload::Message,
1696        status: 504,
1697    },
1698];
1699
1700/// Find an `HttpResult[T]` variant by name. Returns the variant info or
1701/// `None` if the name doesn't match.
1702pub fn http_variant(name: &str) -> Option<HttpVariant> {
1703    HTTP_VARIANTS.iter().copied().find(|v| v.name == name)
1704}
1705
1706/// Payload shape of a `QueueResult` variant (v0.44). Non-generic — a verdict
1707/// carries no value; `Retry` carries a `String` reason for the log path.
1708#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1709pub enum QueueVariantPayload {
1710    /// No payload (`Ack`).
1711    None,
1712    /// Carries a `String` reason (`Retry`).
1713    Message,
1714}
1715
1716/// One variant of the built-in `QueueResult` sum (v0.44).
1717#[derive(Debug, Clone, Copy)]
1718pub struct QueueVariant {
1719    pub name: &'static str,
1720    pub payload: QueueVariantPayload,
1721}
1722
1723/// All `QueueResult` variants, in declaration order. `Ack` confirms the
1724/// message; `Retry` redelivers it, carrying a reason for observability.
1725pub const QUEUE_VARIANTS: &[QueueVariant] = &[
1726    QueueVariant {
1727        name: "Ack",
1728        payload: QueueVariantPayload::None,
1729    },
1730    QueueVariant {
1731        name: "Retry",
1732        payload: QueueVariantPayload::Message,
1733    },
1734];
1735
1736/// Find a `QueueResult` variant by name.
1737pub fn queue_variant(name: &str) -> Option<QueueVariant> {
1738    QUEUE_VARIANTS.iter().copied().find(|v| v.name == name)
1739}
1740
1741#[derive(Debug, Clone)]
1742pub struct TypeDecl {
1743    pub name: Ident,
1744    /// `[T, U]` type parameters (v0.157, ADR 0183): empty for a non-generic
1745    /// type. A generic *record* type (`type Paginated[T] = { … }`) is the only
1746    /// generic body accepted; the checker rejects type parameters on refined /
1747    /// opaque / sum bodies. Mirrors [`FnDecl::type_params`].
1748    pub type_params: Vec<TypeParam>,
1749    pub body: TypeBody,
1750    /// Documentation block attached to this declaration (v0.3).
1751    pub documentation: Option<Doc>,
1752    pub span: Span,
1753    pub trivia: Trivia,
1754}
1755
1756/// `event Name = { fields }` — a typed fact a context may emit and other
1757/// contexts' subscriber services may receive (Events track, slice 0, spine
1758/// #936). Record body only in slice 0 — pattern refinement (subscription
1759/// side, slice 1) and default-valued fields for additive versioning (slice
1760/// 3a) both extend a record body, so nothing here forecloses them. An
1761/// optional `@schema(N)` annotation (slice 3b) asserts the event's current
1762/// wire schema version, embedded into `env.schemaVersion` at emission — see
1763/// [`EventDecl::schema_version`]. Legal only inside a `context` —
1764/// checker-enforced (`bynk.event.outside_context`), not grammar, mirroring
1765/// how `capability`/`provides` are commons-rejected at the parser while
1766/// `event` instead follows `messages`' precedent (ADR 0272) of parsing
1767/// uniformly and letting the checker place it, since unlike
1768/// `capability`/`provides` an `event` has no meaning to reject early inside
1769/// an `adapter` either.
1770#[derive(Debug, Clone)]
1771pub struct EventDecl {
1772    pub name: Ident,
1773    /// Every `@`-annotation attached to this declaration. The parser stays
1774    /// permissive (zero or more, same as `store` field / `messages`
1775    /// annotations); the closed registry (today: `@schema` alone) and its
1776    /// argument shape are a checker concern (`bynk.event.unknown_annotation`
1777    /// / `bynk.event.bad_schema_version`), not a parse error.
1778    pub annotations: Vec<Annotation>,
1779    pub body: RecordBody,
1780    /// Documentation block attached to this declaration.
1781    pub documentation: Option<Doc>,
1782    pub span: Span,
1783    pub trivia: Trivia,
1784}
1785
1786impl EventDecl {
1787    /// A synthetic `TypeDecl` with this event's name and record body, so an
1788    /// event registers into the ordinary `types` symbol table and reuses
1789    /// every existing type-reference/exports/consumes/construction check —
1790    /// no non-generic type parameters, no separate resolution path. Callers
1791    /// that need to know a name is specifically an *event* (owner-only
1792    /// emission, `from Events(E)`/`Events.emit[E]`'s "must be an event, not
1793    /// just any type" gate) track that separately, alongside this.
1794    ///
1795    /// Deliberately lossy: a `TypeDecl` has no `annotations`, so `@schema(N)`
1796    /// does not survive this conversion. Nothing downstream of this
1797    /// synthesis needs the event's schema version — only the emitter's own
1798    /// `Events.emit` lowering does, and it reads [`EventDecl::schema_version`]
1799    /// directly off the real declaration instead.
1800    pub fn as_type_decl(&self) -> TypeDecl {
1801        TypeDecl {
1802            name: self.name.clone(),
1803            type_params: Vec::new(),
1804            body: TypeBody::Record(self.body.clone()),
1805            documentation: self.documentation.clone(),
1806            span: self.span,
1807            trivia: self.trivia.clone(),
1808        }
1809    }
1810
1811    /// This event's declared wire schema version (Events slice 3b, #978):
1812    /// the positive `Int` literal argument of its sole `@schema(N)`
1813    /// annotation, or `1` if the annotation is absent — identical to every
1814    /// event's behaviour before this annotation existed. A malformed
1815    /// `@schema` (non-positive, non-literal, wrong arity, labelled, or
1816    /// duplicated) has already been reported by the checker
1817    /// (`bynk.event.bad_schema_version`); this falls back to `1` rather than
1818    /// re-deriving that diagnostic.
1819    pub fn schema_version(&self) -> i64 {
1820        self.annotations
1821            .iter()
1822            .find(|a| a.name.name == "schema")
1823            .and_then(|a| a.args.first())
1824            .and_then(|arg| match &arg.value.kind {
1825                ExprKind::IntLit { value, .. } if *value > 0 => Some(*value),
1826                _ => None,
1827            })
1828            .unwrap_or(1)
1829    }
1830}
1831
1832/// The structural filter on a `from Events(E { field: value, .. })`
1833/// subscription header (Events track, slice 1, spine #936) — deliver-and-filter:
1834/// every emission still reaches the fan-out mechanism, and the subscriber's
1835/// own generated handler evaluates this as a boolean guard before running the
1836/// body. Deliberately **not** a [`Pattern`] — an event is a plain record, not
1837/// a sum, so it has no tag for [`Pattern::Variant`] to test; extending the
1838/// shared `Pattern` enum to fit would touch parser/checker/emitter/fmt/
1839/// tree-sitter/LSP sites and drag in match-exhaustiveness semantics a
1840/// delivery filter does not need. This amends
1841/// [ADR 0286](../decisions/0286-events-pattern-dispatch-deliver-and-filter.md)'s
1842/// "no bespoke matching engine is introduced for Events" claim; its
1843/// deliver-and-filter decision is unchanged. No static narrowing: a matching
1844/// handler body still sees its parameter at its own declared type, never
1845/// narrowed to a listed field's specific value (deferred — narrowing needs a
1846/// singleton-variant type the checker does not have, and waits on the
1847/// refinement-propagation design question `design/bynk-type-system.md`
1848/// §2.5.4 names as still open).
1849#[derive(Debug, Clone)]
1850pub struct EventPattern {
1851    /// The listed fields, in source order. Never empty — a pattern with no
1852    /// fields has no shape (`from Events(E)`, no braces, is the pattern-less
1853    /// form; `from Events(E { })` is a parse error pointing at it).
1854    pub fields: Vec<EventPatternField>,
1855    /// The span of the required trailing `..` — every listed field leaves
1856    /// the rest of the record's fields unconstrained, and that must be
1857    /// written explicitly rather than implied.
1858    pub rest_span: Span,
1859    pub span: Span,
1860}
1861
1862/// One `name: value` entry in an [`EventPattern`].
1863#[derive(Debug, Clone)]
1864pub struct EventPatternField {
1865    pub name: Ident,
1866    pub value: EventPatternValue,
1867    pub span: Span,
1868}
1869
1870/// The value a pattern field is matched against. A closed set, mirroring
1871/// [`Pattern::Literal`]'s closed literal kinds plus a nullary sum-variant
1872/// reference — no nested record sub-patterns in v1 (slice 1 filters on
1873/// top-level fields only).
1874#[derive(Debug, Clone)]
1875pub enum EventPatternValue {
1876    /// An `Int`/`String`/`Bool` literal — matches the field by value equality.
1877    Literal { value: LiteralValue, span: Span },
1878    /// A nullary sum-type variant, optionally qualified: `Region.Domestic` or
1879    /// bare `Domestic` — both resolve against the field's declared sum type.
1880    /// A variant that carries a payload is rejected (`bynk.event.
1881    /// pattern_variant_payload`): testing only the tag while ignoring a
1882    /// payload would silently over-broaden the filter.
1883    Variant {
1884        /// `Some(Region)` for the qualified form, `None` for bare.
1885        type_name: Option<Ident>,
1886        variant: Ident,
1887        span: Span,
1888    },
1889}
1890
1891impl EventPattern {
1892    pub fn span(&self) -> Span {
1893        self.span
1894    }
1895}
1896
1897impl EventPatternValue {
1898    pub fn span(&self) -> Span {
1899        match self {
1900            EventPatternValue::Literal { span, .. } => *span,
1901            EventPatternValue::Variant { span, .. } => *span,
1902        }
1903    }
1904}
1905
1906/// A `via schema(...)` dispatch clause on a `from Events(...)` header
1907/// (Events track, slice 4, spine #936): filters delivery by the envelope's
1908/// `schemaVersion`, parallel to [`EventPattern`] but matched against the
1909/// envelope rather than the payload, and written after the `Events(...)`
1910/// header's closing `)` rather than inside it. Delivery is still
1911/// deliver-and-filter (unchanged from slice 1's ADR 0286): the fan-out
1912/// mechanism delivers every emission to every subscriber regardless, and
1913/// this becomes one more independently-evaluated runtime guard in the
1914/// subscriber's own generated handler — no cross-subscriber ambiguity
1915/// check (two sibling subscribers with the same or overlapping version
1916/// coverage are both legal, undiagnosed).
1917#[derive(Debug, Clone)]
1918pub struct SchemaDispatch {
1919    pub pattern: SchemaVersionPattern,
1920    pub span: Span,
1921}
1922
1923/// The pattern a `via schema(...)` clause matches `env.schemaVersion`
1924/// against. A closed set of one variant today — literal only, mirroring
1925/// `@schema(N)`'s own permissive-parse-then-checker-validate split (a
1926/// non-positive value is a checker error, not a parse error, for the same
1927/// diagnostic style). A future slice's range patterns (`via schema(2..)`)
1928/// are additive to this enum, not a breaking rename of every match site
1929/// this slice creates.
1930#[derive(Debug, Clone)]
1931pub enum SchemaVersionPattern {
1932    Literal(i64),
1933}
1934
1935/// The right-hand side of a `type` declaration. In v0/v0.1 only the
1936/// `Refined` variant existed; v0.2 adds records and sums; v0.3 adds opaque.
1937#[derive(Debug, Clone)]
1938pub enum TypeBody {
1939    /// Refined base type: `BaseType where refinement`.
1940    Refined {
1941        base: BaseType,
1942        base_span: Span,
1943        refinement: Option<Refinement>,
1944    },
1945    /// Record type: `{ field: T where ..., ... }`.
1946    Record(RecordBody),
1947    /// Sum type: pipe-form variants or `enum { ... }` shorthand.
1948    Sum(SumBody),
1949    /// Opaque base type: `opaque BaseType (where refinement)?` (v0.3 §3.4).
1950    /// Identity is nominal; the base type is hidden outside the defining commons.
1951    Opaque {
1952        base: BaseType,
1953        base_span: Span,
1954        refinement: Option<Refinement>,
1955    },
1956}
1957
1958/// Body of a record-type declaration (v0.2 §3.1).
1959#[derive(Debug, Clone)]
1960pub struct RecordBody {
1961    pub fields: Vec<RecordField>,
1962    pub span: Span,
1963    /// #1788: comments before the closing `}`.
1964    pub trailing_comments: Vec<Comment>,
1965}
1966
1967/// One field of a record type declaration. Each field may carry inline
1968/// refinement, which is enforced at construction time on the field's value.
1969#[derive(Debug, Clone)]
1970pub struct RecordField {
1971    pub name: Ident,
1972    pub type_ref: TypeRef,
1973    pub refinement: Option<Refinement>,
1974    /// v0.11: an optional initial-value expression. Only meaningful on agent
1975    /// `state` fields (the field's fresh-key value); ignored / rejected on
1976    /// record-type fields by the checker.
1977    pub init: Option<Expr>,
1978    pub span: Span,
1979    /// #1888: the doc above the field, in either form. Hover, completion and
1980    /// the documentation page show it.
1981    pub documentation: Option<Doc>,
1982    /// #1788: comments above the field and at the end of its line.
1983    pub trivia: Trivia,
1984}
1985
1986/// Body of a sum-type declaration (v0.2 §3.2).
1987#[derive(Debug, Clone)]
1988pub struct SumBody {
1989    pub variants: Vec<Variant>,
1990    /// v0.154 (ADR 0178): declared error embeddings — `embeds E as V, …` after
1991    /// the variants. Each says "an `E` value auto-wraps into variant `V`", which
1992    /// the `?` operator uses to convert a cross-context error without a manual
1993    /// `.mapErr`. Empty for a sum with no embeddings.
1994    pub embeds: Vec<EmbedsClause>,
1995    pub span: Span,
1996    /// #1794: comments before the closing `}` of an `enum { … }` body, or, in
1997    /// the pipe form, on their own lines between the last variant and its
1998    /// `embeds` clause.
1999    pub trailing_comments: Vec<Comment>,
2000}
2001
2002/// One `embeds <source_type> as <variant>` mapping in a sum body (v0.154, ADR
2003/// 0178). Declares that a value of `source_type` can be auto-wrapped into the
2004/// named single-payload `variant` of the enclosing sum.
2005#[derive(Debug, Clone)]
2006pub struct EmbedsClause {
2007    pub source_type: TypeRef,
2008    pub variant: Ident,
2009    pub span: Span,
2010}
2011
2012/// One variant of a sum type. Variants may have payload fields; a
2013/// payload-less variant is a simple tag.
2014#[derive(Debug, Clone)]
2015pub struct Variant {
2016    pub name: Ident,
2017    pub payload: Vec<VariantField>,
2018    pub span: Span,
2019    /// #1888: the doc above the variant, in either form.
2020    pub documentation: Option<Doc>,
2021    /// #1794: comments above the variant and at the end of its line. A comment
2022    /// on an `enum {` line leads the first variant, and so does one on the `=`
2023    /// line of a pipe-form sum. The last pipe-form variant's end-of-line
2024    /// comment is the type's own trailing comment unless an `embeds` clause
2025    /// follows it.
2026    pub trivia: Trivia,
2027}
2028
2029/// One payload field of a sum variant. Variant payload fields use named
2030/// declarations like record fields, but do not carry refinement in v0.2.
2031#[derive(Debug, Clone)]
2032pub struct VariantField {
2033    pub name: Ident,
2034    pub type_ref: TypeRef,
2035    pub span: Span,
2036    /// #1888: the doc above the payload field, in either form.
2037    pub documentation: Option<Doc>,
2038}
2039
2040#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
2041pub enum BaseType {
2042    Int,
2043    String,
2044    Bool,
2045    Float,
2046    /// `Duration` (v0.86, ADR 0112) — a span of time, a distinct base type
2047    /// erased to TS `number` carrying milliseconds (the `Clock` unit). Modelled
2048    /// on `Float`: Bynk-side-only, no implicit `Int` coercion (save the one
2049    /// sanctioned clock-math mix).
2050    Duration,
2051    /// `Instant` (v0.90, ADR 0114) — an absolute point in time, a distinct base
2052    /// type erased to TS `number` carrying Unix epoch milliseconds (the
2053    /// `Clock` unit). No literal (minted by `Clock.now()`); arithmetic composes
2054    /// with `Duration` (`Instant ± Duration -> Instant`, `Instant − Instant ->
2055    /// Duration`). Supersedes ADR 0112 D4's `Int`↔`Duration` clock-math mix.
2056    Instant,
2057    /// `Bytes` (v0.110, ADR 0142) — an immutable finite octet sequence, the
2058    /// seventh base type. Unlike its neighbours it does **not** erase to TS
2059    /// `number`: a `Bytes` lowers to a `Uint8Array`. No source literal
2060    /// (constructed via `Bytes.fromUtf8`/`fromBase64`/`empty`); `==` compares
2061    /// by content (real emitter codegen, not host `===`); wires as a base64
2062    /// JSON string; not `Map`-keyable and not orderable.
2063    Bytes,
2064}
2065
2066impl BaseType {
2067    pub fn name(self) -> &'static str {
2068        match self {
2069            BaseType::Int => "Int",
2070            BaseType::String => "String",
2071            BaseType::Bool => "Bool",
2072            BaseType::Float => "Float",
2073            BaseType::Duration => "Duration",
2074            BaseType::Instant => "Instant",
2075            BaseType::Bytes => "Bytes",
2076        }
2077    }
2078}
2079
2080/// A `Duration` literal unit (v0.86, ADR 0112) — the closed set of suffixes in a
2081/// `<int>.<unit>` literal. Each maps to a fixed millisecond factor (`Duration`
2082/// erases to `Int` milliseconds).
2083#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2084pub enum DurationUnit {
2085    Milliseconds,
2086    Seconds,
2087    Minutes,
2088    Hours,
2089    Days,
2090}
2091
2092impl DurationUnit {
2093    /// Resolve a unit name (`minutes`) to its variant, or `None` if it is not one
2094    /// of the closed set. Used by the parser to recognise an `<int>.<unit>`
2095    /// literal; an unrecognised name leaves the expression a field access.
2096    pub fn from_name(name: &str) -> Option<Self> {
2097        Some(match name {
2098            "milliseconds" => DurationUnit::Milliseconds,
2099            "seconds" => DurationUnit::Seconds,
2100            "minutes" => DurationUnit::Minutes,
2101            "hours" => DurationUnit::Hours,
2102            "days" => DurationUnit::Days,
2103            _ => return None,
2104        })
2105    }
2106
2107    /// The unit name as written.
2108    pub fn name(self) -> &'static str {
2109        match self {
2110            DurationUnit::Milliseconds => "milliseconds",
2111            DurationUnit::Seconds => "seconds",
2112            DurationUnit::Minutes => "minutes",
2113            DurationUnit::Hours => "hours",
2114            DurationUnit::Days => "days",
2115        }
2116    }
2117
2118    /// The unit's value in milliseconds.
2119    pub fn millis(self) -> i64 {
2120        match self {
2121            DurationUnit::Milliseconds => 1,
2122            DurationUnit::Seconds => 1_000,
2123            DurationUnit::Minutes => 60_000,
2124            DurationUnit::Hours => 3_600_000,
2125            DurationUnit::Days => 86_400_000,
2126        }
2127    }
2128}
2129
2130/// An integer refinement bound (v0.40, ADR 0073): the parsed value plus the
2131/// bound's source span (covering a leading `-`). Value-only beyond the span —
2132/// ints have one canonical printed form, so the formatter stays idempotent
2133/// without a stored lexeme. The span backs the `InRange`-swap quick-fix.
2134#[derive(Debug, Clone)]
2135pub struct IntBound {
2136    pub value: i64,
2137    pub span: Span,
2138}
2139
2140/// A float refinement bound (v0.21): the parsed value plus the signed source
2141/// lexeme (for byte-stable emission). v0.40 (ADR 0073): also the source span,
2142/// for the `InRange`-swap quick-fix.
2143#[derive(Debug, Clone)]
2144pub struct FloatBound {
2145    pub value: f64,
2146    pub lexeme: String,
2147    pub span: Span,
2148}
2149
2150#[derive(Debug, Clone)]
2151pub struct Refinement {
2152    pub predicates: Vec<RefinementPred>,
2153    pub span: Span,
2154}
2155
2156#[derive(Debug, Clone)]
2157pub struct RefinementPred {
2158    pub kind: PredKind,
2159    pub span: Span,
2160}
2161
2162#[derive(Debug, Clone)]
2163pub enum PredKind {
2164    Matches(String),
2165    InRange(IntBound, IntBound),
2166    /// `InRange` with float bounds (v0.21) — a separate variant so every
2167    /// `Int` refinement path stays untouched. Bounds keep their source
2168    /// lexemes (including any sign) so emitted runtime checks are
2169    /// byte-stable.
2170    InRangeF(FloatBound, FloatBound),
2171    MinLength(i64),
2172    MaxLength(i64),
2173    Length(i64),
2174    NonNegative,
2175    Positive,
2176    NonEmpty,
2177}
2178
2179impl PredKind {
2180    pub fn name(&self) -> &'static str {
2181        match self {
2182            PredKind::Matches(_) => "Matches",
2183            PredKind::InRange(..) | PredKind::InRangeF(..) => "InRange",
2184            PredKind::MinLength(_) => "MinLength",
2185            PredKind::MaxLength(_) => "MaxLength",
2186            PredKind::Length(_) => "Length",
2187            PredKind::NonNegative => "NonNegative",
2188            PredKind::Positive => "Positive",
2189            PredKind::NonEmpty => "NonEmpty",
2190        }
2191    }
2192}
2193
2194/// #1651: the order a refinement's predicates are **checked** in at runtime:
2195/// every `Matches` after every other predicate, and otherwise source order.
2196///
2197/// A `Matches` pattern may take time polynomial in the input's length, and a
2198/// `MaxLength`/`Length` bound is what caps it (`bynk.types.polynomial_regex_capped`),
2199/// so the length checks must run first wherever the author wrote them. The order
2200/// is observable only in which failure a value failing several predicates
2201/// reports. Every runtime check site (`.of`, the boundary codec, `is`) and the
2202/// checker's compile-time literal check use this order, so they agree. The
2203/// canonical form a contract fingerprint hashes sorts its own copy and is
2204/// unaffected.
2205pub fn in_check_order<T>(preds: &[T], kind: impl Fn(&T) -> &PredKind) -> Vec<&T> {
2206    let (regex, rest): (Vec<&T>, Vec<&T>) = preds
2207        .iter()
2208        .partition(|p| matches!(kind(p), PredKind::Matches(_)));
2209    rest.into_iter().chain(regex).collect()
2210}
2211
2212/// A function type parameter (v0.20a, `fn name[A, B](…)`). A struct rather
2213/// than a bare Ident so the ADR-0028 "bound-capable" promise is a later field
2214/// addition, not a representation change.
2215#[derive(Debug, Clone)]
2216pub struct TypeParam {
2217    pub name: Ident,
2218    pub span: Span,
2219}
2220
2221/// A lambda expression (v0.20a): `(params) => expr` or `(params) => { … }`.
2222/// `=>` is the value arrow (shared with `match`); param annotations are
2223/// optional where an expected function type supplies them.
2224#[derive(Debug, Clone)]
2225pub struct LambdaExpr {
2226    pub params: Vec<LambdaParam>,
2227    pub body: Box<Expr>,
2228    pub span: Span,
2229}
2230
2231/// A lambda parameter. A separate type from [`Param`] because its annotation
2232/// is optional — `Param.type_ref` stays mandatory at every signature site.
2233#[derive(Debug, Clone)]
2234pub struct LambdaParam {
2235    pub name: Ident,
2236    pub type_ref: Option<TypeRef>,
2237    pub span: Span,
2238}
2239
2240#[derive(Debug, Clone)]
2241pub struct FnDecl {
2242    /// v0.20a: `[A, B]` type parameters; empty for non-generic functions.
2243    pub type_params: Vec<TypeParam>,
2244    /// Free function or method (`TypeName.methodName`). See [`FnName`].
2245    pub name: FnName,
2246    pub params: Vec<Param>,
2247    pub return_type: TypeRef,
2248    /// v0.115: preconditions (`requires <name>: <pred>`), parsed between the
2249    /// return type and the body. A contract clause is the invariant predicate
2250    /// attached to a function (ADR 0144 — one predicate surface); `requires`
2251    /// scopes over the parameters only.
2252    pub requires: Vec<Contract>,
2253    /// v0.115: postconditions (`ensures <name>: <pred>`). Scopes over the
2254    /// parameters *and* `result`, the contextual binding for the return value.
2255    pub ensures: Vec<Contract>,
2256    pub body: Block,
2257    /// True when the first parameter is the special `self` parameter. Only
2258    /// valid for method declarations.
2259    pub has_self: bool,
2260    /// Documentation block attached to this declaration (v0.3).
2261    pub documentation: Option<Doc>,
2262    pub span: Span,
2263    pub trivia: Trivia,
2264}
2265
2266/// A function-declaration name: either a free function `f` or a method
2267/// `T.method` (v0.2 §3.6).
2268#[derive(Debug, Clone)]
2269pub enum FnName {
2270    /// `fn name(...)` — a free function.
2271    Free(Ident),
2272    /// `fn TypeName.methodName(...)` — a method attached to a type.
2273    Method {
2274        type_name: Ident,
2275        method_name: Ident,
2276    },
2277}
2278
2279impl FnName {
2280    /// The function's short name for diagnostics. For methods returns the
2281    /// method portion only; the type prefix is recovered via `type_name`.
2282    pub fn ident(&self) -> &Ident {
2283        match self {
2284            FnName::Free(id) => id,
2285            FnName::Method { method_name, .. } => method_name,
2286        }
2287    }
2288
2289    /// For methods, the attached type's identifier; `None` for free fns.
2290    pub fn type_name(&self) -> Option<&Ident> {
2291        match self {
2292            FnName::Free(_) => None,
2293            FnName::Method { type_name, .. } => Some(type_name),
2294        }
2295    }
2296
2297    /// The displayed full name (e.g., `Money.add` or `parseSku`).
2298    pub fn display(&self) -> String {
2299        match self {
2300            FnName::Free(id) => id.name.clone(),
2301            FnName::Method {
2302                type_name,
2303                method_name,
2304            } => format!("{}.{}", type_name.name, method_name.name),
2305        }
2306    }
2307}
2308
2309/// A brace-delimited block of statements ending in a tail expression
2310/// whose value is the block's value (spec v0.1 §3.1).
2311#[derive(Debug, Clone)]
2312pub struct Block {
2313    pub statements: Vec<Statement>,
2314    pub tail: Box<Expr>,
2315    pub span: Span,
2316    /// Line comments that appear between the last statement (or the
2317    /// opening brace) and the tail expression. Preserved here because
2318    /// expressions do not carry trivia in v1.1.
2319    pub tail_leading_comments: Vec<Comment>,
2320    /// `true` when the block was written with no explicit tail expression and
2321    /// the parser synthesised a `()` (unit) tail (v0.146, ADR 0170). The tail
2322    /// is a real `ExprKind::UnitLit` either way; this flag records that it was
2323    /// *implicit* so the formatter can omit it (Bynk has no statement
2324    /// terminator, so a printed `()` would re-attach to the last statement on
2325    /// re-parse — `x` `()` → `x()`). The parser re-derives the implicit unit
2326    /// tail, so omitting it is loss-free.
2327    pub implicit_tail: bool,
2328}
2329
2330impl Block {
2331    /// Whether this block is a synthesised empty unit block — no statements and
2332    /// an *implicit* `()` tail (v0.146, ADR 0170). This is exactly the shape the
2333    /// parser inserts for an `if` with no `else` branch, so both the checker
2334    /// (gating the else-less form to unit) and the formatter (omitting the
2335    /// synthetic `else { () }`) recognise it here.
2336    pub fn is_synth_unit(&self) -> bool {
2337        self.statements.is_empty()
2338            && self.implicit_tail
2339            && matches!(self.tail.kind, ExprKind::UnitLit)
2340    }
2341}
2342
2343/// Block-level statement.
2344#[derive(Debug, Clone)]
2345pub enum Statement {
2346    /// `let name (: T)? = expr` — pure binding (v0.1).
2347    Let(LetStmt),
2348    /// `let name (: T)? <- expr` — effectful binding (v0.5).
2349    EffectLet(LetStmt),
2350    /// `expect expr` — verify a Bool predicate at test runtime (v0.7; renamed
2351    /// from `assert` in v0.112). Only valid inside test case bodies.
2352    Expect(ExpectStmt),
2353    /// `~> expr` — an asynchronous fire-and-forget send (v0.79). The caller does
2354    /// not await the reply; legal only when the reply is `Effect[()]`. No binder.
2355    Send(SendStmt),
2356    /// `do expr` — an effect-performing expression statement (v0.146, ADR 0170).
2357    /// Runs an `Effect[()]` and discards its (unit) result — the binder-free
2358    /// sugar for `let _ <- expr` when the awaited value is unit. Legal only in
2359    /// an effectful body; the operand MUST be `Effect[()]` (a valued reply keeps
2360    /// the explicit `let _ <- e`, so throwing away a real value stays visible).
2361    Do(DoStmt),
2362    /// `name := expr` — a `Cell` store write (v0.81, storage track). The
2363    /// unconditional write form; `.update(fn)` (a method call) is the
2364    /// read-modify-write form. ADR 0108.
2365    Assign(AssignStmt),
2366}
2367
2368impl Statement {
2369    pub fn span(&self) -> Span {
2370        match self {
2371            Statement::Let(l) | Statement::EffectLet(l) => l.span,
2372            Statement::Expect(a) => a.span,
2373            Statement::Send(s) => s.span,
2374            Statement::Do(d) => d.span,
2375            Statement::Assign(a) => a.span,
2376        }
2377    }
2378}
2379
2380#[derive(Debug, Clone)]
2381pub struct ExpectStmt {
2382    pub value: Expr,
2383    pub span: Span,
2384    pub trivia: Trivia,
2385}
2386
2387/// `name := expr` — a `Cell` store write (v0.81, storage track). `target` is the
2388/// `Cell` field being written (a bare name for now; the checker resolves it to a
2389/// `store` field). `value` is the new value.
2390#[derive(Debug, Clone)]
2391pub struct AssignStmt {
2392    pub target: Ident,
2393    pub value: Expr,
2394    pub span: Span,
2395    pub trivia: Trivia,
2396}
2397
2398#[derive(Debug, Clone)]
2399pub struct LetStmt {
2400    pub name: Ident,
2401    pub type_annot: Option<TypeRef>,
2402    pub value: Expr,
2403    /// v0.182 (#664): the call-site `by <Actor>(<identity>)` clause on an
2404    /// `EffectLet` whose value addresses a test service handler. `None` on a pure
2405    /// `Let` (the `by` is parsed only in the `<-` arm) and on an effect-let with
2406    /// no principal.
2407    pub principal: Option<CallSiteActor>,
2408    pub span: Span,
2409    pub trivia: Trivia,
2410}
2411
2412#[derive(Debug, Clone)]
2413pub struct SendStmt {
2414    /// The send target — a recipient call, e.g. `Logger.info(msg)`.
2415    pub value: Expr,
2416    pub span: Span,
2417    pub trivia: Trivia,
2418}
2419
2420/// `do expr` — an effect-performing expression statement (v0.146, ADR 0170).
2421/// `value` is the awaited effect, which MUST be `Effect[()]`.
2422#[derive(Debug, Clone)]
2423pub struct DoStmt {
2424    pub value: Expr,
2425    pub span: Span,
2426    pub trivia: Trivia,
2427}
2428
2429#[derive(Debug, Clone)]
2430pub struct Param {
2431    pub name: Ident,
2432    pub type_ref: TypeRef,
2433    pub span: Span,
2434}
2435
2436#[derive(Debug, Clone)]
2437pub enum TypeRef {
2438    Base(BaseType, Span),
2439    Named(Ident),
2440    /// `Result[T, E]` — the built-in generic Result type (v0.1).
2441    Result(Box<TypeRef>, Box<TypeRef>, Span),
2442    /// `Option[T]` — the built-in generic Option type (v0.2).
2443    Option(Box<TypeRef>, Span),
2444    /// `Effect[T]` — the built-in generic Effect type (v0.5).
2445    Effect(Box<TypeRef>, Span),
2446    /// `HttpResult[T]` — the built-in HTTP-result sum (v0.9).
2447    HttpResult(Box<TypeRef>, Span),
2448    /// `QueueResult` — the built-in queue verdict sum (`Ack | Retry`),
2449    /// non-generic; the required return of a queue handler (v0.44).
2450    QueueResult(Span),
2451    /// `Asset` — a static file declared by `asset name = "path"` (#1883,
2452    /// slice 1a, [DECISION H]). Non-generic, like `QueueResult`; its one
2453    /// field is `.url : String`.
2454    Asset(Span),
2455    /// `List[T]` — the built-in generic immutable list type (v0.20b).
2456    List(Box<TypeRef>, Span),
2457    /// `Map[K, V]` — the built-in generic immutable map type (v0.20b).
2458    /// Keys are confined to value-keyable types
2459    /// (`bynk.types.unkeyable_map_key`).
2460    Map(Box<TypeRef>, Box<TypeRef>, Span),
2461    /// `Query[T]` — the built-in lazy storage-read description (v0.91, ADR 0115).
2462    /// Nameable in a pure helper's return type; non-storable and non-boundary
2463    /// (like `Effect`/`Fn`).
2464    Query(Box<TypeRef>, Span),
2465    /// `Stream[T]` — the value-over-time primitive (v0.100, real-time track
2466    /// slice 0). A lazy, pull-shaped sequence produced over time; non-storable
2467    /// and non-boundary (like `Query`/`Effect`/`Fn`).
2468    Stream(Box<TypeRef>, Span),
2469    /// `Connection[F]` — a held WebSocket connection (v0.102, real-time track
2470    /// slice 2). `F` is the server→client frame type. A `Held` resource:
2471    /// non-serialisable, non-boundary, and governed by the linearity discipline
2472    /// (§2.9); storable only in `Cell[Option[Connection]]` / `Map[K, Connection]`.
2473    Connection(Box<TypeRef>, Span),
2474    /// `History[Agent]` — a generated, driven call-history of an agent (v0.119,
2475    /// testing track slice 7, ADR 0155). A test-only generator, legal only in
2476    /// `for all` binding position inside a `property`; it is not a value type,
2477    /// so it never resolves in a field/param/return position. The bound subject
2478    /// behaves as an ordinary `List[Step]`.
2479    History(Box<TypeRef>, Span),
2480    /// `ValidationError` — the built-in error type used by refined-type
2481    /// constructors (v0.1).
2482    ValidationError(Span),
2483    /// `JsonError` — the built-in JSON-decode error type (v0.22b). A
2484    /// uniform record (`kind`/`path`/`message`, all `String`) the codec
2485    /// maps `BoundaryError` variants and parse failures into.
2486    JsonError(Span),
2487    /// `()` — the unit type (v0.5).
2488    Unit(Span),
2489    /// `A -> B` / `(A, B) -> C` / `() -> B` — a function type (v0.20a).
2490    /// Right-associative; effectful iff the return type is `Effect[_]`
2491    /// (the structural rule). Confined to non-boundary positions
2492    /// (`bynk.types.function_at_boundary`).
2493    Fn(Vec<TypeRef>, Box<TypeRef>, Span),
2494    /// `Name[Arg, …]` — an application of a user-declared generic type
2495    /// (v0.157, ADR 0183). `name` is a user type name (never a built-in
2496    /// generic, which each have a dedicated variant above). Arity and the
2497    /// existence of the referenced type are checked in the resolver.
2498    App {
2499        name: Ident,
2500        args: Vec<TypeRef>,
2501        span: Span,
2502    },
2503}
2504
2505impl TypeRef {
2506    pub fn span(&self) -> Span {
2507        match self {
2508            TypeRef::Base(_, s) => *s,
2509            TypeRef::Named(id) => id.span,
2510            TypeRef::Result(_, _, s) => *s,
2511            TypeRef::Option(_, s) => *s,
2512            TypeRef::Effect(_, s) => *s,
2513            TypeRef::HttpResult(_, s) => *s,
2514            TypeRef::QueueResult(s) => *s,
2515            TypeRef::Asset(s) => *s,
2516            TypeRef::List(_, s) => *s,
2517            TypeRef::Map(_, _, s) => *s,
2518            TypeRef::Query(_, s) => *s,
2519            TypeRef::Stream(_, s) => *s,
2520            TypeRef::Connection(_, s) => *s,
2521            TypeRef::History(_, s) => *s,
2522            TypeRef::ValidationError(s) => *s,
2523            TypeRef::JsonError(s) => *s,
2524            TypeRef::Unit(s) => *s,
2525            TypeRef::Fn(_, _, s) => *s,
2526            TypeRef::App { span, .. } => *span,
2527        }
2528    }
2529}
2530
2531/// v0.174 (#592): does the generic record type `name` transitively contain a
2532/// reference to itself — through any field-type path, including collection and
2533/// `Option` wrappers, sum-variant payloads, and generic type arguments? Such a
2534/// type has no finite set of monomorphised boundary codecs: uniform recursion
2535/// (`Node[T] = { next: Option[Node[T]] }`) would need a self-referential codec
2536/// chain the per-instantiation model does not yet generate, and polymorphic
2537/// recursion (`Weird[T] = { next: Option[Weird[List[T]]] }`) an unbounded set of
2538/// instantiations. Both are rejected at a boundary
2539/// (`bynk.generics.recursive_generic_at_boundary`).
2540///
2541/// Detection is reachability over the type-containment graph: `name` is
2542/// recursive iff it is reachable from its own body, following every named /
2543/// applied head and descending into every wrapper, map/result pair, function
2544/// position, and generic argument. Terminates via the `visited` set.
2545pub fn generic_record_is_recursive(
2546    name: &str,
2547    types: &std::collections::HashMap<String, std::sync::Arc<TypeDecl>>,
2548) -> bool {
2549    fn heads(t: &TypeRef, out: &mut Vec<String>) {
2550        match t {
2551            TypeRef::Named(id) => out.push(id.name.clone()),
2552            TypeRef::App {
2553                name: app_name,
2554                args,
2555                ..
2556            } => {
2557                out.push(app_name.name.clone());
2558                for a in args {
2559                    heads(a, out);
2560                }
2561            }
2562            TypeRef::Option(a, _)
2563            | TypeRef::List(a, _)
2564            | TypeRef::Effect(a, _)
2565            | TypeRef::HttpResult(a, _)
2566            | TypeRef::Query(a, _)
2567            | TypeRef::Stream(a, _)
2568            | TypeRef::Connection(a, _)
2569            | TypeRef::History(a, _) => heads(a, out),
2570            TypeRef::Result(a, b, _) | TypeRef::Map(a, b, _) => {
2571                heads(a, out);
2572                heads(b, out);
2573            }
2574            TypeRef::Fn(ps, r, _) => {
2575                for p in ps {
2576                    heads(p, out);
2577                }
2578                heads(r, out);
2579            }
2580            TypeRef::Base(..)
2581            | TypeRef::QueueResult(_)
2582            | TypeRef::Asset(_)
2583            | TypeRef::ValidationError(_)
2584            | TypeRef::JsonError(_)
2585            | TypeRef::Unit(_) => {}
2586        }
2587    }
2588    fn body_heads(decl: &TypeDecl, out: &mut Vec<String>) {
2589        match &decl.body {
2590            TypeBody::Record(r) => {
2591                for f in &r.fields {
2592                    heads(&f.type_ref, out);
2593                }
2594            }
2595            TypeBody::Sum(s) => {
2596                for v in &s.variants {
2597                    for p in &v.payload {
2598                        heads(&p.type_ref, out);
2599                    }
2600                }
2601            }
2602            TypeBody::Refined { .. } | TypeBody::Opaque { .. } => {}
2603        }
2604    }
2605    let Some(root) = types.get(name) else {
2606        return false;
2607    };
2608    let mut visited: std::collections::HashSet<String> = std::collections::HashSet::new();
2609    let mut stack: Vec<String> = Vec::new();
2610    body_heads(root, &mut stack);
2611    while let Some(n) = stack.pop() {
2612        if n == name {
2613            return true;
2614        }
2615        if !visited.insert(n.clone()) {
2616            continue;
2617        }
2618        if let Some(decl) = types.get(&n) {
2619            body_heads(decl, &mut stack);
2620        }
2621    }
2622    false
2623}
2624
2625/// T3.4 (R2.4): a node's identity, independent of position — allocated once,
2626/// monotonically, per expression the parser constructs (`Parser::alloc_expr_id`
2627/// in `bynk-syntax/src/parser.rs`). Never derived from a `Span`, so two
2628/// expressions occupying the same byte range (a synthetic node, a
2629/// zero-width span) never collide the way a span-keyed side table could.
2630#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
2631pub struct ExprId(pub u32);
2632
2633impl ExprId {
2634    /// Reserved for `Expr` nodes built outside the parser — after checking,
2635    /// during emission — that are never looked up in a checker-populated
2636    /// `expr_types`/`expr_ty` table (they are lowered directly, from
2637    /// already-typed sub-expressions they wrap or splice). A lookup against
2638    /// this id is a bug: the node was never checked and has no recorded
2639    /// type of its own.
2640    pub const SYNTHETIC: ExprId = ExprId(u32::MAX);
2641}
2642
2643#[derive(Debug, Clone)]
2644pub struct Expr {
2645    pub id: ExprId,
2646    pub kind: ExprKind,
2647    pub span: Span,
2648}
2649
2650/// Finding #31: `Expr` sets the size of every expression node in the
2651/// program — `ExprKind::Observation`'s payload and `ExprKind::Is`'s pattern
2652/// field are boxed specifically to keep it small (176 bytes unboxed, 128
2653/// boxed, measured on this target). Pinned so the next large variant added
2654/// to `ExprKind` is a compile error here rather than a silent regression.
2655/// T3.4: `id: ExprId` adds 4 bytes (padded); the budget is unchanged, so this
2656/// still fits.
2657/// T3.4 (R2.4): `id: ExprId` is a deliberate 8-byte increase (128 → 136,
2658/// alignment-padded from 4), not a silent regression — the ceiling moves
2659/// with it, once, here, so the next *accidental* growth still trips this
2660/// assertion rather than hiding under slack headroom.
2661/// T3.5 (R2.2): `file: FileId` on `Span` is a deliberate increase (136 → 160
2662/// — `Span` itself grows from 16 to 24 bytes with alignment padding, and
2663/// `ExprKind`'s largest variant carries more than one `Span`), not a silent
2664/// regression — the ceiling moves with it, once, here, exactly as T3.4 did
2665/// for `id: ExprId`.
2666const _: () = assert!(std::mem::size_of::<Expr>() <= 160);
2667
2668impl ExprKind {
2669    /// Construct an `IntLit` for a *synthesized* integer — one the compiler
2670    /// invents rather than reading from source (a default `1`, a computed bound).
2671    /// The lexeme is the canonical decimal form (no separators). Source-parsed
2672    /// literals keep their as-written lexeme instead (v0.142, ADR 0166).
2673    pub fn int_lit(value: i64) -> ExprKind {
2674        ExprKind::IntLit {
2675            value,
2676            lexeme: value.to_string(),
2677        }
2678    }
2679}
2680
2681#[derive(Debug, Clone)]
2682pub enum ExprKind {
2683    /// An integer literal (typed `Int`). The lexeme is kept alongside the parsed
2684    /// value (v0.142, ADR 0166) so formatting is byte-stable: an author's `_`
2685    /// digit separators (`1_048_576`) survive a round-trip, mirroring the
2686    /// `FloatLit` treatment. The value is separator-free; emission lowers the
2687    /// value, so emitted output is unaffected.
2688    IntLit {
2689        value: i64,
2690        lexeme: String,
2691    },
2692    /// A float literal (v0.21). The lexeme is kept alongside the parsed
2693    /// value so emission and formatting are byte-stable (`1e10` must not
2694    /// normalise to `10000000000`).
2695    FloatLit {
2696        value: f64,
2697        lexeme: String,
2698    },
2699    /// A duration literal `<int>.<unit>` (v0.86, ADR 0112): `5.minutes`,
2700    /// `30.days`. The parser recognises the `IntLit . <unit>` shape and records
2701    /// the magnitude, the unit, and the resolved milliseconds (the value the
2702    /// emitter lowers to). Typed `Duration`.
2703    DurationLit {
2704        /// The integer magnitude as written (`5` in `5.minutes`).
2705        value: i64,
2706        /// The unit name (`minutes`), one of the closed set.
2707        unit: DurationUnit,
2708        /// The value in milliseconds — `value * unit factor`.
2709        millis: i64,
2710    },
2711    StrLit(String),
2712    /// An interpolated string `"… \(expr) …"` (v0.43, ADR 0075). Chunks and
2713    /// holes alternate. A plain `"…"` with no holes stays [`ExprKind::StrLit`],
2714    /// so existing code and the emitter/formatter fast-path are untouched.
2715    InterpStr(Vec<InterpPart>),
2716    BoolLit(bool),
2717    Ident(Ident),
2718    Call {
2719        name: Ident,
2720        /// v0.20a: explicit type arguments (`name[T](…)`); empty when absent.
2721        type_args: Vec<TypeRef>,
2722        args: Vec<Expr>,
2723    },
2724    /// A lambda (v0.20a). See [`LambdaExpr`].
2725    Lambda(LambdaExpr),
2726    BinOp(BinOp, Box<Expr>, Box<Expr>),
2727    UnaryOp(UnaryOp, Box<Expr>),
2728    Paren(Box<Expr>),
2729    /// `{ stmts; expr }` — block expression (v0.1).
2730    Block(Block),
2731    /// `if cond { then } else { else }` (v0.1).
2732    If {
2733        cond: Box<Expr>,
2734        then_block: Box<Block>,
2735        else_block: Box<Block>,
2736    },
2737    /// `Ok(value)` — Result success constructor (v0.1).
2738    Ok(Box<Expr>),
2739    /// `Err(error)` — Result failure constructor (v0.1).
2740    Err(Box<Expr>),
2741    /// `expr?` — propagation operator (v0.1).
2742    Question(Box<Expr>),
2743    /// `TypeName { field: value, ... }` — record construction (v0.2).
2744    RecordConstruction {
2745        type_name: Ident,
2746        fields: Vec<FieldInit>,
2747    },
2748    /// `receiver.field` — field access on a record value (v0.2). v0.3 adds
2749    /// `.raw` on opaque types within the defining commons.
2750    FieldAccess {
2751        receiver: Box<Expr>,
2752        field: Ident,
2753    },
2754    /// `receiver.method(args)` — instance method call (v0.2). The
2755    /// resolver determines the receiver's type and looks up the method.
2756    MethodCall {
2757        receiver: Box<Expr>,
2758        method: Ident,
2759        /// v0.22b: explicit type arguments on a qualified static
2760        /// (`Json.decode[T](…)`); empty when absent. The same-line-`[`
2761        /// rule applies as for `Call` type application (0039).
2762        type_args: Vec<TypeRef>,
2763        args: Vec<Expr>,
2764    },
2765    /// `match disc { arm+ }` — pattern matching (v0.2).
2766    Match {
2767        discriminant: Box<Expr>,
2768        arms: Vec<MatchArm>,
2769    },
2770    /// `expr is pattern` — pattern test, returns Bool (v0.2).
2771    ///
2772    /// `pattern` is boxed (finding #31): `Pattern`'s `Variant` case carries two
2773    /// `Ident`s plus a `Vec`, inlining it into every `ExprKind` sets the size
2774    /// of every expression node in the program for the one variant that
2775    /// tests a pattern.
2776    Is {
2777        value: Box<Expr>,
2778        pattern: Box<Pattern>,
2779    },
2780    /// `Some(value)` — Option Some constructor (v0.2).
2781    Some(Box<Expr>),
2782    /// `None` — Option None constructor (v0.2).
2783    None,
2784    /// `()` — unit literal (v0.5).
2785    UnitLit,
2786    /// `TypeName { ...base, field: value, ... }` or `{ ...base, ... }` —
2787    /// record spread expression (v0.5).
2788    RecordSpread {
2789        /// Optional type prefix (`TypeName { ...base }`). Absent for the
2790        /// bare form used inside `commit`.
2791        type_name: Option<Ident>,
2792        /// The base record being spread.
2793        base: Box<Expr>,
2794        /// Field overrides (always full `name: value` form — never shorthand).
2795        overrides: Vec<FieldInit>,
2796    },
2797    /// `Effect.pure(value)` — wrap a synchronous value into `Effect[T]`
2798    /// (v0.5). Recognised in the parser as a special-form.
2799    EffectPure(Box<Expr>),
2800    /// `expect expr` — expectation as an expression of type `()` (v0.9.1;
2801    /// renamed from `assert` in v0.112). Valid only inside test bodies. Evaluates
2802    /// `expr` (must be Bool); if false, the surrounding test case fails.
2803    Expect(Box<Expr>),
2804    /// `Val[T]`, `Val[T](args)` — test-context value construction (v0.9.4).
2805    /// `args` is empty for the bare form and holds the pin arguments for
2806    /// `Val[T](...)`. The record-override form `Val[T] { ... }` is not yet
2807    /// parsed. Valid only inside test bodies; has type `T`.
2808    Val {
2809        type_ref: TypeRef,
2810        args: Vec<Expr>,
2811    },
2812    /// `Wire(<String>)` — a raw, pre-validation argument to a `system`-tier
2813    /// service address (testing-the-boundary Slice C). The inner expression is a
2814    /// `String` carrying the wire form the boundary will receive *unvalidated* —
2815    /// a body's JSON text or a path segment — so a case can drive the router with
2816    /// input the type system forbids and observe the rejection. Legal only at
2817    /// `system` (there is no wire at `unit`); the router validates it, so no
2818    /// refined value is ever minted from a `Wire` (ADR 0182 untouched).
2819    Wire(Box<Expr>),
2820    /// `[a, b, c]` — list literal (v0.20b). An empty `[]` requires an
2821    /// expected type (`bynk.types.uninferable_element_type`).
2822    ListLit(Vec<Expr>),
2823    /// An observation over a consumed capability's recorded calls (v0.117,
2824    /// testing track slice 5). The direct subject of an `expect` in a `case`
2825    /// body — `expect Cap.op called once with <pred>`, `expect Cap.op never
2826    /// called`, `expect A.op before B.op`. Types as `Bool` (the claim about the
2827    /// recorded trace), lowered to a boolean over the recorded log.
2828    /// Boxed (finding #31): at ~160 bytes, `ObservationExpr` inlined here set
2829    /// the size of every `ExprKind` for the one variant that records a
2830    /// capability-call observation.
2831    Observation(Box<ObservationExpr>),
2832    /// `<call> faults` — the claim that an effectful call **faults** (#1706).
2833    /// The direct subject of an `expect` in a `case` body —
2834    /// `expect quote.call("GBP") faults`. The claim's `call` is the call
2835    /// itself, an `Effect[_]` the claim awaits; the claim types as `Bool` and
2836    /// holds when awaiting the call throws (a capability fault, an injected
2837    /// `stub … fails`, an invariant violation) rather than returning a value.
2838    /// A fault is untyped and uncatchable by the caller, so this is a test's
2839    /// observation of the fault, not a handler for it: no production code can
2840    /// write it.
2841    ///
2842    /// #1812: the claim may carry a call-site principal (`expect
2843    /// api.POST(…) by User("bob") faults`), so an identity-carrying handler's
2844    /// fault path is testable; see [`FaultsExpr`]. Boxed, as `Observation` is,
2845    /// so the principal does not widen every `ExprKind`.
2846    Faults(Box<FaultsExpr>),
2847    /// `trace(Cap.op)` — the bound-trace escape hatch (v0.117, testing track
2848    /// slice 5). Yields the recorded calls of `Cap.op` as a `List[<CallRecord>]`
2849    /// (a synthetic record of the operation's parameters), asserted over with the
2850    /// ordinary value surface. Test-body-only, like [`ExprKind::Val`].
2851    Trace {
2852        cap: Ident,
2853        op: Ident,
2854    },
2855}
2856
2857/// Every directly-nested sub-expression of `e` — the **total** child
2858/// iterator. The match is exhaustive (no `_` arm), so adding an [`ExprKind`]
2859/// variant is a compile error here rather than a silently incomplete walk —
2860/// the trap the checker's three hand-rolled partial walkers each fell into
2861/// (block statements and match-arm bodies were skipped, so e.g. the `:=`
2862/// self-reference rule was bypassable through a match arm).
2863///
2864/// Exhaustive over [`ExprKind`] *and* over the expressions each variant holds
2865/// (#1760: match-arm guards were once missed, and every walk built on this one
2866/// missed them with it).
2867///
2868/// Descends one level: block *statements* and the tail, match-arm guards and
2869/// bodies, lambda bodies, interpolation holes, record-field values, and
2870/// observation predicates are all children. Callers recurse for a deep walk.
2871pub fn expr_children(e: &Expr) -> Vec<&Expr> {
2872    fn block_children<'a>(b: &'a Block, out: &mut Vec<&'a Expr>) {
2873        for s in &b.statements {
2874            statement_exprs(s, out);
2875        }
2876        out.push(&b.tail);
2877    }
2878    let mut out = Vec::new();
2879    match &e.kind {
2880        ExprKind::IntLit { .. }
2881        | ExprKind::FloatLit { .. }
2882        | ExprKind::DurationLit { .. }
2883        | ExprKind::StrLit(_)
2884        | ExprKind::BoolLit(_)
2885        | ExprKind::Ident(_)
2886        | ExprKind::None
2887        | ExprKind::UnitLit
2888        | ExprKind::Trace { .. } => {}
2889        ExprKind::InterpStr(parts) => {
2890            for p in parts {
2891                if let InterpPart::Hole(h) = p {
2892                    out.push(h.as_ref());
2893                }
2894            }
2895        }
2896        ExprKind::Call { args, .. } | ExprKind::Val { args, .. } | ExprKind::ListLit(args) => {
2897            out.extend(args.iter())
2898        }
2899        ExprKind::Wire(inner) => out.push(inner.as_ref()),
2900        ExprKind::Lambda(l) => out.push(l.body.as_ref()),
2901        ExprKind::BinOp(_, l, r) => {
2902            out.push(l.as_ref());
2903            out.push(r.as_ref());
2904        }
2905        ExprKind::UnaryOp(_, inner)
2906        | ExprKind::Paren(inner)
2907        | ExprKind::Ok(inner)
2908        | ExprKind::Err(inner)
2909        | ExprKind::Question(inner)
2910        | ExprKind::Some(inner)
2911        | ExprKind::EffectPure(inner)
2912        | ExprKind::Expect(inner) => out.push(inner.as_ref()),
2913        ExprKind::Faults(f) => {
2914            // #1812: a call-site principal's identity is evaluated first, as
2915            // an argument to the claimed call — the same order as an
2916            // effect-let's in `statement_exprs`.
2917            if let Some(identity) = f.principal.as_ref().and_then(|p| p.identity.as_deref()) {
2918                out.push(identity);
2919            }
2920            out.push(&f.call);
2921        }
2922        ExprKind::Block(b) => block_children(b, &mut out),
2923        ExprKind::If {
2924            cond,
2925            then_block,
2926            else_block,
2927        } => {
2928            out.push(cond.as_ref());
2929            block_children(then_block, &mut out);
2930            block_children(else_block, &mut out);
2931        }
2932        ExprKind::RecordConstruction { fields, .. } => {
2933            out.extend(fields.iter().filter_map(|f| f.value.as_ref()));
2934        }
2935        ExprKind::FieldAccess { receiver, .. } => out.push(receiver.as_ref()),
2936        ExprKind::MethodCall { receiver, args, .. } => {
2937            out.push(receiver.as_ref());
2938            out.extend(args.iter());
2939        }
2940        ExprKind::Match { discriminant, arms } => {
2941            out.push(discriminant.as_ref());
2942            for arm in arms {
2943                // #1760: the guard, in evaluation order before the body. It is
2944                // an ordinary expression, checked like any other.
2945                if let Some(guard) = &arm.guard {
2946                    out.push(guard);
2947                }
2948                match &arm.body {
2949                    MatchBody::Expr(e) => out.push(e),
2950                    MatchBody::Block(b) => block_children(b, &mut out),
2951                }
2952            }
2953        }
2954        ExprKind::Is { value, .. } => out.push(value.as_ref()),
2955        ExprKind::RecordSpread {
2956            base, overrides, ..
2957        } => {
2958            out.push(base.as_ref());
2959            out.extend(overrides.iter().filter_map(|f| f.value.as_ref()));
2960        }
2961        ExprKind::Observation(obs) => match &obs.matcher {
2962            ObservationMatcher::Called { count, with_pred } => {
2963                if let Some(c) = count {
2964                    out.push(c.as_ref());
2965                }
2966                if let Some(p) = with_pred {
2967                    out.push(p.as_ref());
2968                }
2969            }
2970            ObservationMatcher::NeverCalled | ObservationMatcher::Before { .. } => {}
2971        },
2972    }
2973    out
2974}
2975
2976/// The expressions directly contained in a statement — the statement half of
2977/// [`expr_children`]'s total walk. Exhaustive over [`Statement`] for the same
2978/// reason, and over each statement's expressions: a `let`'s call-site
2979/// principal identity included.
2980pub fn statement_exprs<'a>(s: &'a Statement, out: &mut Vec<&'a Expr>) {
2981    match s {
2982        Statement::Let(l) | Statement::EffectLet(l) => {
2983            // #1766 review: a call-site principal's identity (`by User(who)`)
2984            // is a full expression. It is evaluated first, as an argument to
2985            // the addressed call.
2986            if let Some(identity) = l.principal.as_ref().and_then(|p| p.identity.as_deref()) {
2987                out.push(identity);
2988            }
2989            out.push(&l.value)
2990        }
2991        Statement::Expect(a) => out.push(&a.value),
2992        Statement::Send(snd) => out.push(&snd.value),
2993        Statement::Do(d) => out.push(&d.value),
2994        Statement::Assign(a) => out.push(&a.value),
2995    }
2996}
2997
2998/// A fault claim, `<call> [by <Actor>(<identity>)] faults` (#1706, #1812).
2999/// `call` is the effectful call the claim awaits; `principal` is the optional
3000/// call-site `by` clause, read exactly as on an effect-let (`let r <- call by
3001/// User("bob")`): it supplies the identity the addressed handler reads.
3002#[derive(Debug, Clone)]
3003pub struct FaultsExpr {
3004    pub call: Expr,
3005    pub principal: Option<CallSiteActor>,
3006}
3007
3008/// An observation of a capability operation's recorded calls (v0.117, testing
3009/// track slice 5). `cap`/`op` name the seam (`Logger.log`); `matcher` is the
3010/// claim about the recorded calls.
3011#[derive(Debug, Clone)]
3012pub struct ObservationExpr {
3013    pub cap: Ident,
3014    pub op: Ident,
3015    pub matcher: ObservationMatcher,
3016}
3017
3018/// The claim an [`ObservationExpr`] makes about a seam's recorded calls (v0.117).
3019#[derive(Debug, Clone)]
3020pub enum ObservationMatcher {
3021    /// `called` [`once` | `<n> times`]? [`with` `<pred>`]?. `count` is `None`
3022    /// for a bare `called` (at least one); `Some(expr)` is the exact-count claim
3023    /// (a literal; `once` desugars to `1`). `with_pred` matches a call whose
3024    /// arguments (in scope by the operation's parameter names) satisfy it.
3025    Called {
3026        count: Option<Box<Expr>>,
3027        with_pred: Option<Box<Expr>>,
3028    },
3029    /// `never called` — zero calls.
3030    NeverCalled,
3031    /// `before Cap.op` — the first call of the subject precedes the first call
3032    /// of the named operation (both must have occurred).
3033    Before { cap: Ident, op: Ident },
3034}
3035
3036/// One part of an interpolated string (v0.43, ADR 0075). An
3037/// [`ExprKind::InterpStr`] holds an alternating run of these.
3038#[derive(Debug, Clone)]
3039pub enum InterpPart {
3040    /// Literal text between holes, with escapes already resolved.
3041    Chunk(String),
3042    /// An interpolated expression `\(expr)`. Type-checked by the hole rule
3043    /// (base scalars only; see the checker) and lowered into a template-
3044    /// literal `${…}` slot.
3045    Hole(Box<Expr>),
3046}
3047
3048/// One field-initialiser inside a record construction expression:
3049/// either `name: expr` or the shorthand `name` (which requires a binding
3050/// of the same name in scope and uses its value).
3051#[derive(Debug, Clone)]
3052pub struct FieldInit {
3053    pub name: Ident,
3054    /// `None` means shorthand — the field's value is the same-named binding.
3055    pub value: Option<Expr>,
3056    pub span: Span,
3057}
3058
3059/// One arm of a `match` expression: `pattern => body` or, with a guard,
3060/// `pattern if guard => body` (guard added in the nested-patterns increment,
3061/// ADR 0169). A guarded arm matches only when the pattern matches **and** the
3062/// `Bool` guard evaluates true; it never contributes to exhaustiveness.
3063#[derive(Debug, Clone)]
3064pub struct MatchArm {
3065    pub pattern: Pattern,
3066    /// Optional `if <Bool-expr>` guard between the pattern and `=>`.
3067    pub guard: Option<Expr>,
3068    pub body: MatchBody,
3069    pub span: Span,
3070}
3071
3072/// The right-hand side of a match arm — either a single expression or
3073/// a block.
3074#[derive(Debug, Clone)]
3075pub enum MatchBody {
3076    Expr(Expr),
3077    Block(Block),
3078}
3079
3080impl MatchBody {
3081    pub fn span(&self) -> Span {
3082        match self {
3083            MatchBody::Expr(e) => e.span,
3084            MatchBody::Block(b) => b.span,
3085        }
3086    }
3087}
3088
3089/// A pattern (v0.2 §3.8). Patterns appear in `match` arms and as the
3090/// right-hand side of the `is` operator.
3091#[derive(Debug, Clone)]
3092pub enum Pattern {
3093    /// `_` — matches any value, no bindings.
3094    Wildcard(Span),
3095    /// A lowercase identifier — binds the whole value to `name` and matches
3096    /// anything (ADR 0169). At the top of a `match` arm it binds the scrutinee
3097    /// (`n if n > 0 => …`); inside a payload position it binds the field
3098    /// (`Some(user)`). The uppercase-led counterpart is a nullary [`Pattern::Variant`].
3099    Binding(Ident),
3100    /// A literal pattern — `31`, `"english"`, `true` (v0.130 §2.3.4). Matches a
3101    /// primitive scrutinee (`Int`/`String`/`Bool`) by value equality. The
3102    /// admitted set mirrors ADR 0001's closed literal set (integers — including
3103    /// a leading unary minus — strings, and booleans); `Float`/`()` are not
3104    /// admitted as patterns.
3105    Literal { value: LiteralValue, span: Span },
3106    /// `Variant` or `Variant(bindings)` or `TypeName.Variant(bindings)`. Each
3107    /// payload binding is itself a [`Pattern`] (ADR 0169), so payloads nest:
3108    /// `Some(Ok(x))`, `Err(PollClosed)`.
3109    Variant {
3110        /// Optional qualifier: `TypeName.Variant`.
3111        type_name: Option<Ident>,
3112        /// The variant name.
3113        variant: Ident,
3114        /// Payload bindings (empty for nullary variants).
3115        bindings: Vec<PatternBinding>,
3116        span: Span,
3117    },
3118    /// `p 'where' refinement-predicate` — a refinement guard on a pattern
3119    /// (#472). Matches when `inner` matches *and* the scrutinee satisfies
3120    /// `predicate` at runtime. v1 admits only `Wildcard` as `inner` (no
3121    /// binding form yet); refutable — never counts toward exhaustiveness or
3122    /// as a catch-all arm, the same treatment as an `if` guard (§2.3.4).
3123    Refined {
3124        inner: Box<Pattern>,
3125        predicate: Refinement,
3126        span: Span,
3127    },
3128    /// `p₁ | p₂ | … | pₙ` — an or-pattern (#474 §2.3.4): matches if any
3129    /// alternative matches. Left-associative `|`, flattened by the parser's
3130    /// chain fold into one `Vec` — an alternative is always a leaf
3131    /// (`Wildcard`/`Binding`/`Literal`/`Variant`), never itself an `Or`
3132    /// (there is no parenthesized-pattern syntax to nest one inside another).
3133    /// Well-typedness (checked, not parsed): every alternative binds the same
3134    /// set of names, a name shared across alternatives has the same type
3135    /// (including refinement) in each, and every alternative matches the same
3136    /// value type.
3137    Or(Vec<Pattern>, Span),
3138}
3139
3140/// The value carried by a [`Pattern::Literal`]. A closed set (ADR 0001):
3141/// integer, string, and boolean. Kept distinct from [`ExprKind`] so patterns
3142/// carry only what they can actually match, and so it is `Eq`/`Hash` for the
3143/// duplicate-arm check.
3144#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3145pub enum LiteralValue {
3146    Int(i64),
3147    Str(String),
3148    Bool(bool),
3149}
3150
3151impl LiteralValue {
3152    /// A human-readable rendering for diagnostics (`31`, `"english"`, `true`).
3153    pub fn describe(&self) -> String {
3154        match self {
3155            LiteralValue::Int(n) => n.to_string(),
3156            LiteralValue::Str(s) => format!("{s:?}"),
3157            LiteralValue::Bool(b) => b.to_string(),
3158        }
3159    }
3160}
3161
3162impl Pattern {
3163    pub fn span(&self) -> Span {
3164        match self {
3165            Pattern::Wildcard(s) => *s,
3166            Pattern::Binding(id) => id.span,
3167            Pattern::Literal { span, .. } => *span,
3168            Pattern::Variant { span, .. } => *span,
3169            Pattern::Refined { span, .. } => *span,
3170            Pattern::Or(_, span) => *span,
3171        }
3172    }
3173
3174    /// Every identifier this pattern binds into scope, recursively (`_` and
3175    /// nullary variants bind nothing). Used by the resolver and the checker to
3176    /// populate an arm's scope, and by the guard to see the arm's bindings.
3177    ///
3178    /// For [`Pattern::Or`] this returns the *first* alternative's names — the
3179    /// checker separately verifies (#474 Rule 1) that every alternative binds
3180    /// the same set, so this is a defensive default when that rule is
3181    /// violated, not a semantic choice among alternatives.
3182    pub fn bound_names(&self) -> Vec<&Ident> {
3183        match self {
3184            Pattern::Wildcard(_) | Pattern::Literal { .. } => Vec::new(),
3185            Pattern::Binding(id) => vec![id],
3186            Pattern::Variant { bindings, .. } => bindings
3187                .iter()
3188                .flat_map(|b| b.pattern().bound_names())
3189                .collect(),
3190            Pattern::Refined { inner, .. } => inner.bound_names(),
3191            Pattern::Or(alts, _) => alts.first().map(Pattern::bound_names).unwrap_or_default(),
3192        }
3193    }
3194
3195    /// True when this pattern matches every value and binds nothing — a bare
3196    /// `_`. A [`Pattern::Binding`] also matches everything but *does* bind, so it
3197    /// is not a pure wildcard.
3198    pub fn is_wildcard(&self) -> bool {
3199        matches!(self, Pattern::Wildcard(_))
3200    }
3201
3202    /// True when this pattern matches every value (a `_` or a name binding),
3203    /// i.e. it is irrefutable and covers the position for exhaustiveness. An
3204    /// [`Pattern::Or`] is irrefutable when any alternative is — `_` in any
3205    /// position already makes the whole pattern match everything.
3206    pub fn is_irrefutable(&self) -> bool {
3207        match self {
3208            Pattern::Wildcard(_) | Pattern::Binding(_) => true,
3209            Pattern::Or(alts, _) => alts.iter().any(Pattern::is_irrefutable),
3210            _ => false,
3211        }
3212    }
3213}
3214
3215/// A single binding inside a variant pattern. Two surface forms:
3216/// `pattern` (positional — match the i-th payload field) and
3217/// `fieldName: pattern` (named — match the named payload field). The matched
3218/// sub-`pattern` is a full [`Pattern`] (ADR 0169), so a plain `name` is a
3219/// [`Pattern::Binding`], `_` a [`Pattern::Wildcard`], and `Ok(x)` a nested
3220/// [`Pattern::Variant`].
3221#[derive(Debug, Clone)]
3222pub struct PatternBinding {
3223    /// Source form: positional or named.
3224    pub kind: PatternBindingKind,
3225    pub span: Span,
3226}
3227
3228#[derive(Debug, Clone)]
3229pub enum PatternBindingKind {
3230    /// `pattern` (e.g. `x`, `_`, `Ok(v)`): match the payload field at this position.
3231    Positional { pattern: Pattern },
3232    /// `field: pattern`: match the named payload field against `pattern`.
3233    Named { field: Ident, pattern: Pattern },
3234}
3235
3236impl PatternBinding {
3237    /// The sub-pattern this binding matches its payload field against.
3238    pub fn pattern(&self) -> &Pattern {
3239        match &self.kind {
3240            PatternBindingKind::Positional { pattern } => pattern,
3241            PatternBindingKind::Named { pattern, .. } => pattern,
3242        }
3243    }
3244
3245    /// True when this binding discards its field (`_` or `field: _`) — a pure
3246    /// wildcard sub-pattern that binds nothing.
3247    pub fn is_wildcard(&self) -> bool {
3248        self.pattern().is_wildcard()
3249    }
3250}
3251
3252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3253pub enum BinOp {
3254    /// `P implies Q` — logical implication (v0.80). Desugars to `!P || Q`; sits
3255    /// at the lowest precedence (below `||`). Reads directionally (P → Q).
3256    Implies,
3257    Or,
3258    And,
3259    Eq,
3260    NotEq,
3261    Lt,
3262    LtEq,
3263    Gt,
3264    GtEq,
3265    Add,
3266    Sub,
3267    Mul,
3268    Div,
3269}
3270
3271impl BinOp {
3272    pub fn name(self) -> &'static str {
3273        match self {
3274            BinOp::Implies => "implies",
3275            BinOp::Or => "||",
3276            BinOp::And => "&&",
3277            BinOp::Eq => "==",
3278            BinOp::NotEq => "!=",
3279            BinOp::Lt => "<",
3280            BinOp::LtEq => "<=",
3281            BinOp::Gt => ">",
3282            BinOp::GtEq => ">=",
3283            BinOp::Add => "+",
3284            BinOp::Sub => "-",
3285            BinOp::Mul => "*",
3286            BinOp::Div => "/",
3287        }
3288    }
3289}
3290
3291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3292pub enum UnaryOp {
3293    Neg,
3294    Not,
3295}
3296
3297impl UnaryOp {
3298    pub fn name(self) -> &'static str {
3299        match self {
3300            UnaryOp::Neg => "-",
3301            UnaryOp::Not => "!",
3302        }
3303    }
3304}
3305
3306#[cfg(test)]
3307mod size_tests {
3308    use super::*;
3309
3310    /// Finding #31: boxing `ExprKind::Observation`'s payload and
3311    /// `ExprKind::Is`'s pattern field took `Expr` from 176 to 128 bytes on
3312    /// this target (the module-level `const _` assertion is the real pin;
3313    /// this test just makes the before/after concrete and fails loudly if a
3314    /// future change silently regresses the win rather than tripping the
3315    /// `<= 128` ceiling by enough to notice).
3316    #[test]
3317    fn expr_is_smaller_than_before_the_boxing() {
3318        assert!(
3319            std::mem::size_of::<Expr>() < 176,
3320            "Expr should be smaller than its pre-#31 size of 176 bytes"
3321        );
3322    }
3323}
3324
3325#[cfg(test)]
3326mod expr_children_tests {
3327    use super::*;
3328
3329    /// #1760: a match arm's guard is a child, between the discriminant and
3330    /// the arm's body, in evaluation order.
3331    #[test]
3332    fn a_match_arms_guard_is_a_child_in_evaluation_order() {
3333        let source = "commons d\n\nfn f(n: Int, lim: Int) -> Int {\n  match n {\n    k if k > lim => 1\n    _ => 0\n  }\n}\n";
3334        let tokens = crate::lexer::tokenize(source).unwrap();
3335        let units = crate::parser::parse_units(&tokens, source).unwrap();
3336        let SourceUnit::Commons(c) = &units[0] else {
3337            panic!("a commons");
3338        };
3339        let CommonsItem::Fn(f) = &c.items[0] else {
3340            panic!("a fn");
3341        };
3342        let ExprKind::Match { .. } = &f.body.tail.kind else {
3343            panic!("a match tail");
3344        };
3345        let children: Vec<&str> = expr_children(&f.body.tail)
3346            .into_iter()
3347            .map(|e| &source[e.span.start..e.span.end])
3348            .collect();
3349        assert_eq!(children, ["n", "k > lim", "1", "0"]);
3350    }
3351
3352    /// #1766 review: a `let`'s call-site principal identity is a statement
3353    /// expression, before the value it addresses.
3354    #[test]
3355    fn a_principal_identity_is_a_statement_expression() {
3356        let source = "suite demo.s {\n  case \"c\" {\n    let who = \"alice\"\n    let item <- api.get() by User(who)\n    expect item\n  }\n}\n";
3357        let tokens = crate::lexer::tokenize(source).unwrap();
3358        let units = crate::parser::parse_units(&tokens, source).unwrap();
3359        let SourceUnit::Suite(t) = &units[0] else {
3360            panic!("a suite");
3361        };
3362        let mut exprs = Vec::new();
3363        statement_exprs(&t.cases[0].body.statements[1], &mut exprs);
3364        let texts: Vec<&str> = exprs
3365            .into_iter()
3366            .map(|e| &source[e.span.start..e.span.end])
3367            .collect();
3368        assert_eq!(texts, ["who", "api.get()"]);
3369    }
3370}
3371
3372/// #1832: [`expr_children`] and [`statement_exprs`] reach every child slot of
3373/// every expression, so every walk built on them does too.
3374///
3375/// The guard is in two parts:
3376///
3377/// - **An oracle that cannot fall behind the AST.** [`oracle_children`] and
3378///   [`oracle_statement`] destructure every field of every `ExprKind` variant
3379///   and of every expression-bearing struct with no `..`, so adding a variant
3380///   or a field anywhere an expression can hide is a compile error here until
3381///   the oracle names the new slot.
3382/// - **A program that uses every slot.** [`COVERAGE`] is parsed (never
3383///   checked, so it only has to be syntactically valid), and two tests run
3384///   over it: every slot the oracle knows is used at least once, and at every
3385///   node `expr_children`/`statement_exprs` return exactly the oracle's
3386///   children. A new slot therefore fails the first test until the program
3387///   uses it, and the second until `expr_children` walks it.
3388///
3389/// The hand-rolled walks that cannot be built on `expr_children` (they carry
3390/// scopes, ownership or position) are tested where they live (#1832's
3391/// triage: the resolver, structure, linearity, effects and emit walks).
3392#[cfg(test)]
3393mod slot_coverage_tests {
3394    use super::*;
3395    use std::collections::BTreeSet;
3396
3397    /// Every slot, used at least once. `p` is a commons for the production
3398    /// forms; the suite holds the test-only ones (`Val`, `Wire`,
3399    /// observations, `faults`, `trace`, call-site principals).
3400    const COVERAGE: &str = r#"commons p
3401
3402fn f(x: Int) -> Int {
3403  let a = "s \(x) t"
3404  let b = g(x)
3405  let c = (y) => y
3406  let d = x + 1
3407  let e = -x
3408  let h = (x)
3409  let i = (z) => {
3410    let q = z
3411    q
3412  }
3413  let j = if x > 0 {
3414    let q = 1
3415    q
3416  } else {
3417    let w = 2
3418    w
3419  }
3420  let k = Ok(x)
3421  let l = Err(x)
3422  let n = r?
3423  let o = Some(x)
3424  let u = R { a: x }
3425  let v = r.a
3426  let w = r.m(x)
3427  let y = match x {
3428    z if z > 0 => z
3429    0 => expect x
3430    _ => {
3431      let s = 1
3432      s
3433    }
3434  }
3435  let aa = x is Some(v)
3436  let bb = R { ...r, a: x }
3437  let cc = Effect.pure(x)
3438  let dd = [x]
3439  ~> L.log(x)
3440  do s.put(x)
3441  cell := x
3442  x
3443}
3444"#;
3445
3446    const COVERAGE_SUITE: &str = r#"suite p {
3447  case "c" {
3448    let who = "alice"
3449    let a <- api.get(x) by User(who)
3450    let b = Val[T](x)
3451    let c = Wire(x)
3452    expect L.log called 2 times with msg == x
3453    expect box.call() faults
3454    expect api.post(x) by User(who) faults
3455    let t = trace(L.log)
3456    expect x
3457  }
3458}
3459"#;
3460
3461    type Slots<'a> = Vec<(&'static str, &'a Expr)>;
3462
3463    fn oracle_block<'a>(owner: &'static str, b: &'a Block, out: &mut Slots<'a>) {
3464        let Block {
3465            statements,
3466            tail,
3467            span: _,
3468            tail_leading_comments: _,
3469            implicit_tail: _,
3470        } = b;
3471        for s in statements {
3472            for (slot, e) in oracle_statement(s) {
3473                out.push((slot, e));
3474            }
3475        }
3476        out.push((owner, tail));
3477    }
3478
3479    /// A statement's expressions, each tagged with its slot.
3480    fn oracle_statement(s: &Statement) -> Slots<'_> {
3481        fn let_slots<'a>(kind: &'static str, l: &'a LetStmt, out: &mut Slots<'a>) {
3482            let LetStmt {
3483                name: _,
3484                type_annot: _,
3485                value,
3486                principal,
3487                span: _,
3488                trivia: _,
3489            } = l;
3490            if let Some(CallSiteActor {
3491                actor: _,
3492                identity,
3493                span: _,
3494            }) = principal
3495                && let Some(identity) = identity
3496            {
3497                out.push((
3498                    if kind == "let" {
3499                        "let.identity"
3500                    } else {
3501                        "let<-.identity"
3502                    },
3503                    identity,
3504                ));
3505            }
3506            out.push((
3507                if kind == "let" {
3508                    "let.value"
3509                } else {
3510                    "let<-.value"
3511                },
3512                value,
3513            ));
3514        }
3515        let mut out = Vec::new();
3516        match s {
3517            Statement::Let(l) => let_slots("let", l, &mut out),
3518            Statement::EffectLet(l) => let_slots("let<-", l, &mut out),
3519            Statement::Expect(ExpectStmt {
3520                value,
3521                span: _,
3522                trivia: _,
3523            }) => out.push(("expect.value", value)),
3524            Statement::Send(SendStmt {
3525                value,
3526                span: _,
3527                trivia: _,
3528            }) => out.push(("send.value", value)),
3529            Statement::Do(DoStmt {
3530                value,
3531                span: _,
3532                trivia: _,
3533            }) => out.push(("do.value", value)),
3534            Statement::Assign(AssignStmt {
3535                target: _,
3536                value,
3537                span: _,
3538                trivia: _,
3539            }) => out.push(("assign.value", value)),
3540        }
3541        out
3542    }
3543
3544    /// An expression's children, each tagged with its slot.
3545    fn oracle_children(e: &Expr) -> Slots<'_> {
3546        let Expr {
3547            id: _,
3548            kind,
3549            span: _,
3550        } = e;
3551        let mut out = Vec::new();
3552        match kind {
3553            ExprKind::IntLit {
3554                value: _,
3555                lexeme: _,
3556            }
3557            | ExprKind::FloatLit {
3558                value: _,
3559                lexeme: _,
3560            }
3561            | ExprKind::DurationLit {
3562                value: _,
3563                unit: _,
3564                millis: _,
3565            }
3566            | ExprKind::StrLit(_)
3567            | ExprKind::BoolLit(_)
3568            | ExprKind::Ident(_)
3569            | ExprKind::None
3570            | ExprKind::UnitLit
3571            | ExprKind::Trace { cap: _, op: _ } => {}
3572            ExprKind::InterpStr(parts) => {
3573                for p in parts {
3574                    match p {
3575                        InterpPart::Chunk(_) => {}
3576                        InterpPart::Hole(h) => out.push(("interp.hole", h.as_ref())),
3577                    }
3578                }
3579            }
3580            ExprKind::Call {
3581                name: _,
3582                type_args: _,
3583                args,
3584            } => args.iter().for_each(|a| out.push(("call.arg", a))),
3585            ExprKind::Lambda(LambdaExpr {
3586                params: _,
3587                body,
3588                span: _,
3589            }) => out.push(("lambda.body", body)),
3590            ExprKind::BinOp(_, l, r) => {
3591                out.push(("binop.left", l));
3592                out.push(("binop.right", r));
3593            }
3594            ExprKind::UnaryOp(_, x) => out.push(("unary.operand", x)),
3595            ExprKind::Paren(x) => out.push(("paren.inner", x)),
3596            ExprKind::Block(b) => oracle_block("block.tail", b, &mut out),
3597            ExprKind::If {
3598                cond,
3599                then_block,
3600                else_block,
3601            } => {
3602                out.push(("if.cond", cond));
3603                oracle_block("if.then.tail", then_block, &mut out);
3604                oracle_block("if.else.tail", else_block, &mut out);
3605            }
3606            ExprKind::Ok(x) => out.push(("ok.inner", x)),
3607            ExprKind::Err(x) => out.push(("err.inner", x)),
3608            ExprKind::Question(x) => out.push(("question.inner", x)),
3609            ExprKind::RecordConstruction {
3610                type_name: _,
3611                fields,
3612            } => {
3613                for FieldInit {
3614                    name: _,
3615                    value,
3616                    span: _,
3617                } in fields
3618                {
3619                    if let Some(v) = value {
3620                        out.push(("record.field", v));
3621                    }
3622                }
3623            }
3624            ExprKind::FieldAccess { receiver, field: _ } => out.push(("field.receiver", receiver)),
3625            ExprKind::MethodCall {
3626                receiver,
3627                method: _,
3628                type_args: _,
3629                args,
3630            } => {
3631                out.push(("method.receiver", receiver));
3632                args.iter().for_each(|a| out.push(("method.arg", a)));
3633            }
3634            ExprKind::Match { discriminant, arms } => {
3635                out.push(("match.discriminant", discriminant));
3636                for MatchArm {
3637                    pattern: _,
3638                    guard,
3639                    body,
3640                    span: _,
3641                } in arms
3642                {
3643                    if let Some(g) = guard {
3644                        out.push(("match.guard", g));
3645                    }
3646                    match body {
3647                        MatchBody::Expr(x) => out.push(("match.body", x)),
3648                        MatchBody::Block(b) => oracle_block("match.block.tail", b, &mut out),
3649                    }
3650                }
3651            }
3652            ExprKind::Is { value, pattern: _ } => out.push(("is.value", value)),
3653            ExprKind::Some(x) => out.push(("some.inner", x)),
3654            ExprKind::RecordSpread {
3655                type_name: _,
3656                base,
3657                overrides,
3658            } => {
3659                out.push(("spread.base", base));
3660                for FieldInit {
3661                    name: _,
3662                    value,
3663                    span: _,
3664                } in overrides
3665                {
3666                    if let Some(v) = value {
3667                        out.push(("spread.override", v));
3668                    }
3669                }
3670            }
3671            ExprKind::EffectPure(x) => out.push(("effect_pure.inner", x)),
3672            ExprKind::Expect(x) => out.push(("expect_expr.inner", x)),
3673            ExprKind::Val { type_ref: _, args } => {
3674                args.iter().for_each(|a| out.push(("val.arg", a)))
3675            }
3676            ExprKind::Wire(x) => out.push(("wire.inner", x)),
3677            ExprKind::ListLit(items) => items.iter().for_each(|i| out.push(("list.item", i))),
3678            ExprKind::Observation(obs) => {
3679                let ObservationExpr {
3680                    cap: _,
3681                    op: _,
3682                    matcher,
3683                } = obs.as_ref();
3684                match matcher {
3685                    ObservationMatcher::Called { count, with_pred } => {
3686                        if let Some(c) = count {
3687                            out.push(("observation.count", c));
3688                        }
3689                        if let Some(p) = with_pred {
3690                            out.push(("observation.with", p));
3691                        }
3692                    }
3693                    ObservationMatcher::NeverCalled
3694                    | ObservationMatcher::Before { cap: _, op: _ } => {}
3695                }
3696            }
3697            ExprKind::Faults(f) => {
3698                let FaultsExpr { call, principal } = f.as_ref();
3699                if let Some(CallSiteActor {
3700                    actor: _,
3701                    identity,
3702                    span: _,
3703                }) = principal
3704                    && let Some(identity) = identity
3705                {
3706                    out.push(("faults.identity", identity));
3707                }
3708                out.push(("faults.inner", call));
3709            }
3710        }
3711        out
3712    }
3713
3714    /// Every slot [`oracle_children`] and [`oracle_statement`] can name.
3715    const ALL_SLOTS: &[&str] = &[
3716        "interp.hole",
3717        "call.arg",
3718        "lambda.body",
3719        "binop.left",
3720        "binop.right",
3721        "unary.operand",
3722        "paren.inner",
3723        "block.tail",
3724        "if.cond",
3725        "if.then.tail",
3726        "if.else.tail",
3727        "ok.inner",
3728        "err.inner",
3729        "question.inner",
3730        "record.field",
3731        "field.receiver",
3732        "method.receiver",
3733        "method.arg",
3734        "match.discriminant",
3735        "match.guard",
3736        "match.body",
3737        "match.block.tail",
3738        "is.value",
3739        "some.inner",
3740        "spread.base",
3741        "spread.override",
3742        "effect_pure.inner",
3743        "expect_expr.inner",
3744        "val.arg",
3745        "wire.inner",
3746        "list.item",
3747        "observation.count",
3748        "observation.with",
3749        "faults.identity",
3750        "faults.inner",
3751        "let.value",
3752        "let<-.value",
3753        "let<-.identity",
3754        "expect.value",
3755        "send.value",
3756        "do.value",
3757        "assign.value",
3758    ];
3759
3760    /// Every top-level block of the coverage program: fn bodies and case
3761    /// bodies.
3762    fn bodies(units: &[SourceUnit]) -> Vec<&Block> {
3763        let mut out = Vec::new();
3764        for u in units {
3765            match u {
3766                SourceUnit::Commons(c) => {
3767                    for item in &c.items {
3768                        if let CommonsItem::Fn(f) = item {
3769                            out.push(&f.body);
3770                        }
3771                    }
3772                }
3773                SourceUnit::Suite(t) => out.extend(t.cases.iter().map(|c| &c.body)),
3774                _ => {}
3775            }
3776        }
3777        out
3778    }
3779
3780    fn parse(source: &str) -> Vec<SourceUnit> {
3781        let tokens = crate::lexer::tokenize(source).expect("lex");
3782        crate::parser::parse_units(&tokens, source).expect("the coverage program parses")
3783    }
3784
3785    /// Visit every expression under `b` through the oracle, depth first.
3786    fn visit<'a>(b: &'a Block, f: &mut impl FnMut(&'a Expr)) {
3787        fn go<'a>(e: &'a Expr, f: &mut impl FnMut(&'a Expr)) {
3788            f(e);
3789            for (_, c) in oracle_children(e) {
3790                go(c, f);
3791            }
3792        }
3793        let mut top = Vec::new();
3794        oracle_block("body.tail", b, &mut top);
3795        for (_, e) in top {
3796            go(e, f);
3797        }
3798    }
3799
3800    #[test]
3801    fn the_coverage_program_uses_every_slot() {
3802        let mut used = BTreeSet::new();
3803        for source in [COVERAGE, COVERAGE_SUITE] {
3804            let units = parse(source);
3805            for b in bodies(&units) {
3806                for s in &b.statements {
3807                    used.extend(oracle_statement(s).into_iter().map(|(slot, _)| slot));
3808                }
3809                visit(b, &mut |e| {
3810                    used.extend(oracle_children(e).into_iter().map(|(slot, _)| slot));
3811                    if let ExprKind::Block(b) = &e.kind {
3812                        for s in &b.statements {
3813                            used.extend(oracle_statement(s).into_iter().map(|(slot, _)| slot));
3814                        }
3815                    }
3816                });
3817            }
3818        }
3819        let missing: Vec<&str> = ALL_SLOTS
3820            .iter()
3821            .copied()
3822            .filter(|s| !used.contains(s))
3823            .collect();
3824        let unknown: Vec<&str> = used
3825            .iter()
3826            .copied()
3827            .filter(|s| !ALL_SLOTS.contains(s))
3828            .collect();
3829        assert!(
3830            missing.is_empty() && unknown.is_empty(),
3831            "slots the coverage program never uses: {missing:?}; slots missing from ALL_SLOTS: {unknown:?}"
3832        );
3833    }
3834
3835    #[test]
3836    fn expr_children_returns_exactly_the_oracle_s_children_at_every_node() {
3837        let key = |e: &Expr| (e.span.start, e.span.end, e.id);
3838        let mut checked = 0;
3839        for source in [COVERAGE, COVERAGE_SUITE] {
3840            let units = parse(source);
3841            for b in bodies(&units) {
3842                for s in &b.statements {
3843                    let mut got = Vec::new();
3844                    statement_exprs(s, &mut got);
3845                    let got: Vec<_> = got.into_iter().map(key).collect();
3846                    let want: Vec<_> = oracle_statement(s)
3847                        .into_iter()
3848                        .map(|(_, e)| key(e))
3849                        .collect();
3850                    assert_eq!(got, want, "statement_exprs at {:?}", s.span());
3851                }
3852                visit(b, &mut |e| {
3853                    let got: Vec<_> = expr_children(e).into_iter().map(key).collect();
3854                    let want: Vec<_> = oracle_children(e)
3855                        .into_iter()
3856                        .map(|(_, c)| key(c))
3857                        .collect();
3858                    assert_eq!(
3859                        got,
3860                        want,
3861                        "expr_children of `{}`",
3862                        &source[e.span.start..e.span.end]
3863                    );
3864                    checked += 1;
3865                });
3866            }
3867        }
3868        assert!(checked > 50, "only {checked} nodes checked");
3869    }
3870}