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