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 entry is the body of one `--` line
18/// (the text after the marker, with its original inline whitespace
19/// preserved).
20/// - `trailing` holds a single comment that appears on the same source
21/// line as the node's final token (e.g. `expr -- note`).
22#[derive(Debug, Clone, Default)]
23pub struct Trivia {
24 pub leading: Vec<String>,
25 pub trailing: Option<String>,
26}
27
28impl Trivia {
29 pub fn is_empty(&self) -> bool {
30 self.leading.is_empty() && self.trailing.is_none()
31 }
32}
33
34/// A whole parsed commons source file.
35///
36/// In v0.3 a commons may be split across multiple files in a directory; the
37/// resolver merges them into one logical commons. Each parsed AST instance
38/// represents the contribution from a single source file.
39#[derive(Debug, Clone)]
40pub struct Commons {
41 pub name: QualifiedName,
42 pub items: Vec<CommonsItem>,
43 /// `uses` clauses declared in this file.
44 pub uses: Vec<UsesDecl>,
45 /// Optional documentation block attached to the commons declaration.
46 pub documentation: Option<String>,
47 /// Surface form of the file: brace-delimited body or headerless fragment.
48 pub form: CommonsForm,
49 pub span: Span,
50 /// Trivia attached to the commons declaration itself — leading comments
51 /// before the `commons` keyword and a trailing comment after the header
52 /// or closing brace.
53 pub trivia: Trivia,
54 /// Comments appearing after the last item but before the file ends
55 /// (or the closing brace, for brace form). One entry per `--` line.
56 pub trailing_comments: Vec<String>,
57}
58
59/// The two surface forms in which a commons body may be parsed (v0.3 §3.1).
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum CommonsForm {
62 /// `commons name { ... }`
63 Brace,
64 /// `commons name` followed by top-level declarations to EOF.
65 Fragment,
66}
67
68/// A `uses other.commons` declaration (v0.3 §3.3).
69#[derive(Debug, Clone)]
70pub struct UsesDecl {
71 pub target: QualifiedName,
72 pub span: Span,
73 pub trivia: Trivia,
74}
75
76/// A whole parsed context source file (v0.4 §3.1).
77///
78/// Contexts are the architectural-layer declaration kind. Like commons, a
79/// context may be split across multiple files in a directory.
80#[derive(Debug, Clone)]
81pub struct Context {
82 pub name: QualifiedName,
83 pub items: Vec<CommonsItem>,
84 /// `uses` clauses declared in this file.
85 pub uses: Vec<UsesDecl>,
86 /// `consumes` clauses declared in this file.
87 pub consumes: Vec<ConsumesDecl>,
88 /// `exports` clauses declared in this file.
89 pub exports: Vec<ExportsDecl>,
90 /// Optional documentation block attached to the context declaration.
91 pub documentation: Option<String>,
92 /// Surface form of the file: brace-delimited body or headerless fragment.
93 pub form: CommonsForm,
94 pub span: Span,
95 /// Trivia attached to the context declaration itself — leading comments
96 /// before the `context` keyword.
97 pub trivia: Trivia,
98 /// Comments appearing after the last item but before the file ends
99 /// (or the closing brace, for brace form). One entry per `--` line.
100 pub trailing_comments: Vec<String>,
101}
102
103/// A `consumes other.context` declaration (v0.4 §3.2). May optionally carry
104/// an alias introduced by `consumes other.context as Alias` (v0.6 §3.1).
105#[derive(Debug, Clone)]
106pub struct ConsumesDecl {
107 pub target: QualifiedName,
108 pub alias: Option<Ident>,
109 /// v0.17: `consumes U { Cap, … }` — selected capabilities flattened into
110 /// the consumer's local capability namespace under their bare names (§3.3).
111 /// `None` for the whole-unit forms; `Some` (possibly empty) for the braced
112 /// form. Mutually exclusive with `alias`.
113 pub selected: Option<Vec<Ident>>,
114 pub span: Span,
115 pub trivia: Trivia,
116}
117
118/// An `exports visibility { names }` clause (v0.4 §3.3) or, v0.15, an
119/// `exports capability { names }` clause.
120#[derive(Debug, Clone)]
121pub struct ExportsDecl {
122 pub kind: ExportKind,
123 pub names: Vec<Ident>,
124 pub span: Span,
125 pub trivia: Trivia,
126}
127
128/// What an `exports` clause exposes: types (with a visibility) or, v0.15,
129/// capabilities offered for cross-context consumption.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum ExportKind {
132 /// `exports opaque { ... }` / `exports transparent { ... }` — type exports.
133 Type(Visibility),
134 /// `exports capability { ... }` — capabilities offered to consumers (v0.15).
135 Capability,
136}
137
138/// Visibility level for an exports clause (v0.4 §3.3).
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum Visibility {
141 /// Token-only outside the context: hold, pass, compare; no inspect, no construct.
142 Opaque,
143 /// Readable shape outside the context: inspect fields, match variants; no construct.
144 Transparent,
145}
146
147/// An `adapter qualified.name { … }` declaration (v0.17 §3.1). An adapter
148/// co-locates a capability contract with a non-Bynk binding: it may declare
149/// capabilities, the boundary types they reference, inline pure helper
150/// `type`/`fn` (and `uses`), external (bodiless) providers, `exports
151/// capability`, and exactly one `binding` clause. It may *not* declare
152/// services, agents, or bodied providers. Like commons/contexts it may be
153/// split across files in a directory.
154#[derive(Debug, Clone)]
155pub struct AdapterDecl {
156 pub name: QualifiedName,
157 pub items: Vec<CommonsItem>,
158 /// `uses` clauses declared in this file (pure-vocabulary mixin; allowed
159 /// because helpers cannot pierce containment — spec [DECISION B]).
160 pub uses: Vec<UsesDecl>,
161 /// `exports capability { … }` clauses (adapters export capabilities and
162 /// boundary types, never services).
163 pub exports: Vec<ExportsDecl>,
164 /// v0.18: `consumes U { Cap, … }` clauses — adapter-to-adapter capability
165 /// dependencies (spec §4.5, \[N\]). Braced form only; adapter targets only
166 /// (both enforced semantically, not in the parser).
167 pub consumes: Vec<ConsumesDecl>,
168 /// The `binding "<module>" requires { … }` clause, if present. Required
169 /// when the adapter declares any external provider (`bynk.adapter.no_binding`).
170 pub binding: Option<BindingDecl>,
171 pub documentation: Option<String>,
172 pub form: CommonsForm,
173 pub span: Span,
174 pub trivia: Trivia,
175 pub trailing_comments: Vec<String>,
176}
177
178/// A `binding "<module>" requires { "pkg": "range", … }` clause inside an
179/// adapter (v0.17 §3.5). `module` is the TypeScript module supplying the
180/// adapter's external provider symbols, resolved relative to the adapter's
181/// source file. `requires` declares npm dependencies folded into the
182/// generated `package.json`.
183#[derive(Debug, Clone)]
184pub struct BindingDecl {
185 /// The module path as written (the string-literal contents, no quotes).
186 pub module: String,
187 pub module_span: Span,
188 pub requires: Vec<RequiresDep>,
189 pub span: Span,
190 pub trivia: Trivia,
191}
192
193/// One `"pkg": "range"` entry in a binding's `requires { … }` map.
194#[derive(Debug, Clone)]
195pub struct RequiresDep {
196 pub package: String,
197 pub range: String,
198 pub span: Span,
199}
200
201/// Either a commons or a context — the two declaration kinds at the file
202/// level (v0.4 §3.1). v0.7 adds the test declaration kind; v0.17 the adapter.
203#[derive(Debug, Clone)]
204pub enum SourceUnit {
205 Commons(Commons),
206 Context(Context),
207 Suite(SuiteDecl),
208 /// v0.17: an `adapter` unit — the host boundary (capability contract +
209 /// external binding).
210 Adapter(AdapterDecl),
211}
212
213impl SourceUnit {
214 pub fn name(&self) -> &QualifiedName {
215 match self {
216 SourceUnit::Commons(c) => &c.name,
217 SourceUnit::Context(c) => &c.name,
218 SourceUnit::Suite(t) => &t.target,
219 SourceUnit::Adapter(a) => &a.name,
220 }
221 }
222
223 pub fn span(&self) -> Span {
224 match self {
225 SourceUnit::Commons(c) => c.span,
226 SourceUnit::Context(c) => c.span,
227 SourceUnit::Suite(t) => t.span,
228 SourceUnit::Adapter(a) => a.span,
229 }
230 }
231
232 pub fn kind_name(&self) -> &'static str {
233 match self {
234 SourceUnit::Commons(_) => "commons",
235 SourceUnit::Context(_) => "context",
236 SourceUnit::Suite(_) => "suite",
237 SourceUnit::Adapter(_) => "adapter",
238 }
239 }
240}
241
242/// A `test <qualified-name> { ... }` declaration (v0.7 §3.1).
243///
244/// A test targets a commons or context by qualified name and bundles a set of
245/// test cases plus optional mock declarations. As with commons and contexts, a
246/// test may be split across multiple files (fragment form).
247#[derive(Debug, Clone)]
248pub struct SuiteDecl {
249 /// The targeted commons or context.
250 pub target: QualifiedName,
251 /// `uses` clauses brought in by this test fragment.
252 pub uses: Vec<UsesDecl>,
253 /// v0.118: suite-scoped `provides` clauses — per-seam provider overrides
254 /// applied to every case (a case-scoped `provides` takes precedence).
255 pub provides: Vec<ProvidesClause>,
256 /// The individual test cases.
257 pub cases: Vec<Case>,
258 /// v0.114: generative `property` blocks (testing track slice 2).
259 pub properties: Vec<PropertyDecl>,
260 /// v0.118: the suite-level tier default (`suite … as integration`). `None`
261 /// means the `unit` default; a `case`'s own tier overrides it. A `property`
262 /// ignores a suite tier (tiers are a `case`-only affordance).
263 pub tier: Option<TestTier>,
264 /// Surface form: brace-delimited body or headerless fragment.
265 pub form: CommonsForm,
266 /// Optional documentation block attached to the test declaration.
267 pub documentation: Option<String>,
268 pub span: Span,
269 pub trivia: Trivia,
270 pub trailing_comments: Vec<String>,
271}
272
273/// v0.118: the tier a `case` runs at (testing track slice 6, ADR 0153). One
274/// body promoted across the testing pyramid; `unit` is the default and elided.
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum TestTier {
277 /// Collaborators stubbed (the default).
278 Unit,
279 /// Real collaborators within one context, no serialisation wire.
280 Integration,
281 /// Contexts wired across the real serialise → JSON → deserialise boundary.
282 System,
283}
284
285impl TestTier {
286 pub fn as_str(self) -> &'static str {
287 match self {
288 TestTier::Unit => "unit",
289 TestTier::Integration => "integration",
290 TestTier::System => "system",
291 }
292 }
293}
294
295/// v0.118: a per-seam provider override `provides Cap.method(<args>) returns <v>
296/// | fails` (testing track slice 6, ADR 0154). Substitutes one capability
297/// method's provision under test; the right-hand side is a value or a fault,
298/// never a computed body.
299#[derive(Debug, Clone)]
300pub struct ProvidesClause {
301 /// The capability being overridden (a consumed seam of the unit).
302 pub capability: Ident,
303 /// The overridden method.
304 pub method: Ident,
305 /// One argument pattern per parameter (`_` or a value the arg must equal).
306 pub args: Vec<ArgPattern>,
307 /// The provision: a value, a fault, or a per-call sequence.
308 pub rhs: ProvidesRhs,
309 pub documentation: Option<String>,
310 pub span: Span,
311 pub trivia: Trivia,
312}
313
314/// v0.118: one argument pattern in a `provides` call pattern. Patterns for the
315/// same method are tried top-to-bottom, first match wins.
316#[derive(Debug, Clone)]
317pub enum ArgPattern {
318 /// `_` — matches any argument.
319 Any(Span),
320 /// A value the recorded argument must equal (a literal or pure value expr).
321 Value(Expr),
322}
323
324/// v0.118: the right-hand side of a `provides` clause.
325#[derive(Debug, Clone)]
326pub enum ProvidesRhs {
327 /// `returns <value>` — a single success value, repeated for every call.
328 Returns(Expr),
329 /// `fails` — inject a capability fault (Principle 3).
330 Fails(Span),
331 /// `returns each [<outcome>, …]` — one outcome per call, in order; the last
332 /// outcome repeats once the sequence is exhausted (DECISION V).
333 ReturnsEach(Vec<SeqOutcome>, Span),
334}
335
336impl ProvidesRhs {
337 pub fn span(&self) -> Span {
338 match self {
339 ProvidesRhs::Returns(e) => e.span,
340 ProvidesRhs::Fails(s) => *s,
341 ProvidesRhs::ReturnsEach(_, s) => *s,
342 }
343 }
344}
345
346/// v0.118: one outcome in a sequenced (`returns each`) `provides`.
347#[derive(Debug, Clone)]
348pub enum SeqOutcome {
349 /// A success value.
350 Value(Expr),
351 /// A fault.
352 Fails(Span),
353}
354
355/// A `case "name" [as <tier>] { [provides …] body }` block inside a suite
356/// (v0.7 §3.3; v0.118 adds the tier clause and case-scoped `provides`).
357#[derive(Debug, Clone)]
358pub struct Case {
359 /// The test name, taken from the string literal.
360 pub name: String,
361 /// The span of the string literal — used for diagnostics and runtime
362 /// failure reports.
363 pub name_span: Span,
364 /// v0.118: the case's own tier, if written (`as integration` / `as system`).
365 /// `None` means inherit the suite default (itself `unit` when unset).
366 pub tier: Option<TestTier>,
367 /// v0.118: case-scoped `provides` clauses (override the suite's, and the
368 /// tier default).
369 pub provides: Vec<ProvidesClause>,
370 pub body: Block,
371 pub documentation: Option<String>,
372 pub span: Span,
373 pub trivia: Trivia,
374}
375
376/// A `property "name" { for all <bindings> [where <pred>] { body } }` block
377/// inside a suite (v0.114, testing track slice 2, ADR 0149). The generative
378/// sibling of [`Case`]: the runner draws inhabitants of each binding's type from
379/// its refinement domain and evaluates the body's `expect`s over them.
380#[derive(Debug, Clone)]
381pub struct PropertyDecl {
382 /// The property name, taken from the string literal.
383 pub name: String,
384 /// The span of the string literal — used for diagnostics and reports.
385 pub name_span: Span,
386 /// The `for all` binder: the generated bindings, an optional `where` filter,
387 /// and the predicate body.
388 pub forall: ForAll,
389 pub documentation: Option<String>,
390 pub span: Span,
391 pub trivia: Trivia,
392}
393
394/// The `for all x: T, … [where <pred>] { … }` binder inside a [`PropertyDecl`].
395#[derive(Debug, Clone)]
396pub struct ForAll {
397 /// The generated bindings, `x: T` (one or more).
398 pub bindings: Vec<ForAllBinding>,
399 /// An optional `where <pred>` filter (a pure `Bool`) applied to generated
400 /// tuples before the body runs.
401 pub where_pred: Option<Expr>,
402 /// The body — one or more statements, typically `expect`s.
403 pub body: Block,
404 pub span: Span,
405}
406
407/// One `for all` binding: `name: T`, where the runner generates inhabitants of
408/// `T` from its refinements.
409#[derive(Debug, Clone)]
410pub struct ForAllBinding {
411 pub name: Ident,
412 pub type_ref: TypeRef,
413}
414
415/// A capability reference in a `given` clause (v0.15 §3.2). A bare name is a
416/// local capability (`given Cap`); a dotted name refers to a capability a
417/// consumed context provides (`given B.Cap` / `given Alias.Cap`).
418#[derive(Debug, Clone)]
419pub struct CapRef {
420 /// `None` for a local capability; `Some(prefix)` for a cross-context
421 /// reference where `prefix` is a consumed-context qualified name or alias.
422 pub context: Option<QualifiedName>,
423 /// The capability's simple name (also the local deps key).
424 pub name: Ident,
425 pub span: Span,
426}
427
428impl CapRef {
429 /// The local deps key / capability simple name (e.g. `Clock`).
430 pub fn key(&self) -> &str {
431 &self.name.name
432 }
433
434 /// True when this references a capability provided by a consumed context.
435 pub fn is_cross_context(&self) -> bool {
436 self.context.is_some()
437 }
438
439 /// The cross-context prefix (consumed-context qualified name or alias) as
440 /// a dotted string, if any.
441 pub fn prefix(&self) -> Option<String> {
442 self.context.as_ref().map(|q| q.joined())
443 }
444}
445
446/// A dotted name like `fitness.units`.
447#[derive(Debug, Clone)]
448pub struct QualifiedName {
449 pub parts: Vec<Ident>,
450 pub span: Span,
451}
452
453impl QualifiedName {
454 pub fn joined(&self) -> String {
455 self.parts
456 .iter()
457 .map(|p| p.name.as_str())
458 .collect::<Vec<_>>()
459 .join(".")
460 }
461}
462
463#[derive(Debug, Clone)]
464pub enum CommonsItem {
465 Type(TypeDecl),
466 Fn(FnDecl),
467 /// `capability Name { fn op(...) -> T ... }` (v0.5; contexts only).
468 Capability(CapabilityDecl),
469 /// `provides Cap = ProviderName { fn op(...) -> T { ... } ... }` (v0.5).
470 Provider(ProviderDecl),
471 /// `service Name { on call(...) -> T { ... } ... }` (v0.5).
472 Service(ServiceDecl),
473 /// `agent Name { key id: T; state { ... }; on call ... }` (v0.5).
474 Agent(AgentDecl),
475 /// `actor Name { auth = Scheme, identity = T }` (v0.45). A nominal boundary
476 /// contract consumed by a handler's `by` clause; not a runnable entity.
477 Actor(ActorDecl),
478}
479
480impl CommonsItem {
481 pub fn name(&self) -> &Ident {
482 match self {
483 CommonsItem::Type(t) => &t.name,
484 CommonsItem::Fn(f) => f.name.ident(),
485 CommonsItem::Capability(c) => &c.name,
486 CommonsItem::Provider(p) => &p.provider_name,
487 CommonsItem::Service(s) => &s.name,
488 CommonsItem::Agent(a) => &a.name,
489 CommonsItem::Actor(a) => &a.name,
490 }
491 }
492}
493
494/// A capability declaration (v0.5 §3.3). Capabilities are interface-like
495/// contracts for external dependencies, used inside contexts. They may only
496/// appear inside a `context` declaration.
497#[derive(Debug, Clone)]
498pub struct CapabilityDecl {
499 pub name: Ident,
500 pub ops: Vec<CapabilityOp>,
501 pub documentation: Option<String>,
502 pub span: Span,
503 pub trivia: Trivia,
504}
505
506/// One operation in a capability (signature only; no body).
507#[derive(Debug, Clone)]
508pub struct CapabilityOp {
509 pub name: Ident,
510 pub params: Vec<Param>,
511 pub return_type: TypeRef,
512 pub documentation: Option<String>,
513 pub span: Span,
514 pub trivia: Trivia,
515}
516
517/// A provider declaration (v0.5 §3.4). Supplies an implementation for a
518/// capability.
519#[derive(Debug, Clone)]
520pub struct ProviderDecl {
521 /// The capability being implemented.
522 pub capability: Ident,
523 /// The provider's identifier (used in tests/config to select impls).
524 pub provider_name: Ident,
525 /// v0.12: capabilities this provider depends on (`provides X = Impl given
526 /// Y, Z { … }`). The provider's operation bodies may use these. v0.15:
527 /// a dependency may be a cross-context capability (`given B.Cap`).
528 pub given: Vec<CapRef>,
529 pub ops: Vec<ProviderOp>,
530 /// v0.17: an *external* provider — `provides Cap = Name` with **no** brace
531 /// block — inside an adapter, supplied by the adapter's binding rather than
532 /// a Bynk body. When `true`, `ops` is empty and the emitter produces no
533 /// class. The absence of the brace block (not an empty one) is the signal.
534 pub external: bool,
535 pub documentation: Option<String>,
536 pub span: Span,
537 pub trivia: Trivia,
538}
539
540/// One operation in a provider (signature plus body).
541#[derive(Debug, Clone)]
542pub struct ProviderOp {
543 pub name: Ident,
544 pub params: Vec<Param>,
545 pub return_type: TypeRef,
546 pub body: Block,
547 pub span: Span,
548 pub trivia: Trivia,
549}
550
551/// A service declaration (v0.5 §3.5). Services are the boundary interface
552/// of a context.
553#[derive(Debug, Clone)]
554pub struct ServiceDecl {
555 pub name: Ident,
556 /// The protocol the service conforms to, from the `from <protocol>` header
557 /// clause (v0.44). `Call` when there is no clause.
558 pub protocol: ServiceProtocol,
559 pub handlers: Vec<Handler>,
560 pub documentation: Option<String>,
561 pub span: Span,
562 pub trivia: Trivia,
563}
564
565/// The protocol a service conforms to — declared on the header via
566/// `from <protocol>` (v0.44). `Call` is the default (no `from` clause): a
567/// contract-mediated internal-RPC surface, not a wire protocol. Multi-endpoint
568/// protocols (`Http`, `Cron`) carry no binding — the endpoint lives on each
569/// handler; single-binding `Queue` carries its queue name.
570#[derive(Debug, Clone)]
571pub enum ServiceProtocol {
572 /// No `from` clause: the service holds `on call` handlers only.
573 Call,
574 /// `from http` — many routes; each handler is `on <Method>("route")`.
575 Http,
576 /// `from cron` — many schedules; each handler is `on schedule("expr")`.
577 Cron,
578 /// `from queue("name")` — one bound queue; handlers are `on message(...)`.
579 Queue { name: String },
580 /// `from WebSocket(in: ClientFrame, out: ServerFrame)` — a held WebSocket
581 /// connection (v0.103, real-time track slice 3). `in_type` is the inbound
582 /// frame type (client→server, decoded and routed as typed agent messages);
583 /// `out_type` is the server→client frame type the held `Connection[out_type]`
584 /// carries. The service holds exactly one `on open` handler (edge auth via
585 /// `by`, then transfer of the connection to an agent).
586 WebSocket { in_type: TypeRef, out_type: TypeRef },
587}
588
589/// An agent declaration (v0.5 §3.6). Agents are state-bearing entities
590/// with their own handlers.
591#[derive(Debug, Clone)]
592pub struct AgentDecl {
593 pub name: Ident,
594 /// `key id: Type` — the identifier-typed value identifying instances.
595 pub key_name: Ident,
596 pub key_type: TypeRef,
597 /// `store` fields (v0.81, storage track) — each an access-pattern slot of a
598 /// declared storage kind (`Cell`/`Map`/…). The successor to the removed
599 /// `state { }` record (ADR 0108); every agent declares its state this way.
600 pub store_fields: Vec<StoreField>,
601 /// Invariants (v0.80 §14) — universally-quantified predicates over the
602 /// agent's `store` fields. The phase sits between the fields and the
603 /// handlers; each is checked against the state staged by a handler's writes
604 /// before it commits.
605 pub invariants: Vec<Invariant>,
606 /// Step invariants (v0.116 §, testing track slice 4) — named predicates over
607 /// the pre-/post-commit state *pair* (`old`/`new`), checked at the commit
608 /// boundary beside [`invariants`], from the second commit onward. Widen the
609 /// invariant subject from a snapshot to a step (ADR 0144 — one predicate
610 /// surface).
611 ///
612 /// [`invariants`]: AgentDecl::invariants
613 pub transitions: Vec<Transition>,
614 pub handlers: Vec<Handler>,
615 pub documentation: Option<String>,
616 pub span: Span,
617 pub trivia: Trivia,
618}
619
620/// A `store` field (v0.81, storage track). Each is an access-pattern slot of a
621/// declared storage kind: `store <name>: <Kind>[…] [@annotations] [= <init>]`.
622/// The kind and its element type are carried as an ordinary [`TypeRef`]
623/// (`Cell[Int]`, `Map[K, V]`); the checker restricts which heads are storage
624/// kinds. Access-pattern annotations (`@indexed`, …) parse into [`annotations`]
625/// (v0.85, ADR 0111); the checker validates them against the closed registry.
626///
627/// [`annotations`]: StoreField::annotations
628#[derive(Debug, Clone)]
629pub struct StoreField {
630 pub name: Ident,
631 /// The storage kind and its element type(s): `Cell[Int]`, `Map[K, V]`. A
632 /// dedicated [`StoreKind`] rather than a [`TypeRef`] — storage kinds are not
633 /// value types, and the checker dispatches kind-aware operations on the head.
634 pub kind: StoreKind,
635 /// Storage annotations on the field (v0.85, ADR 0111): `@ttl(5.minutes)`,
636 /// `@indexed(by: orderId)`. Parsed in declaration order (after the kind,
637 /// before the initialiser); the checker validates names against the closed
638 /// registry and gates each to the slice that implements it.
639 pub annotations: Vec<Annotation>,
640 /// The fresh-key initial value (`= expr`), if given — same disposition as a
641 /// `state` field's initialiser (ADRs 0003/0004 carry forward).
642 pub init: Option<Expr>,
643 pub documentation: Option<String>,
644 pub span: Span,
645 pub trivia: Trivia,
646}
647
648/// A storage annotation on a `store` field (v0.85, storage track; ADR 0111):
649/// `@<name>(<args>)`. The `name` is matched against the closed registry
650/// (`@indexed`/`@ttl`/`@retain`/`@bounded`) by the checker; the grammar accepts
651/// any identifier so an unknown name is a checker diagnostic, not a parse error.
652/// Arguments are compile-time metadata, restricted to literals (and the `by:`
653/// field-name labels of `@indexed`) by the checker per ADR 0111 D4.
654#[derive(Debug, Clone)]
655pub struct Annotation {
656 pub name: Ident,
657 pub args: Vec<AnnotationArg>,
658 pub span: Span,
659}
660
661/// A single annotation argument (v0.85; ADR 0111): an optional `label:` followed
662/// by a value expression — `by: orderId` (labelled) or `5.minutes` (positional).
663/// The value is parsed as an ordinary [`Expr`] so the duration-literal form
664/// (`5.minutes`, landing with the `Duration` slice) needs no special grammar;
665/// the checker restricts it to a literal where the annotation is functional.
666#[derive(Debug, Clone)]
667pub struct AnnotationArg {
668 pub label: Option<Ident>,
669 pub value: Expr,
670 pub span: Span,
671}
672
673/// A storage kind applied to its element type(s) (v0.81): `Cell[Int]`,
674/// `Map[ReservationId, Reservation]`. The `head` is the kind name (`Cell`,
675/// `Map`, `Set`, `Log`, `Queue`, `Cache`); the checker validates it against the
676/// closed catalogue. Element types are ordinary [`TypeRef`]s. Refined element
677/// types (`Cell[Int where NonNegative]`) ride a later slice (parse_type_ref does
678/// not yet accept an inline refinement in type-argument position).
679#[derive(Debug, Clone)]
680pub struct StoreKind {
681 pub head: Ident,
682 pub args: Vec<TypeRef>,
683 pub span: Span,
684}
685
686/// An agent invariant (v0.80 §14). A named predicate over the agent's state
687/// fields that must hold of every committed state; a commit that would violate
688/// it faults (`InvariantViolation`) before the state is persisted. The
689/// predicate references state fields by bare name, mirroring the design-notes
690/// worked examples (`status == Paid implies paymentRef.isSome()`).
691#[derive(Debug, Clone)]
692pub struct Invariant {
693 pub name: Ident,
694 /// The predicate expression — an ordinary `Bool`-typed expression over the
695 /// state fields, plus `implies` and `is`. The parsed-predicate-on-a-
696 /// declaration shape mirrors [`ActorRefinement::predicate`].
697 pub predicate: Expr,
698 pub documentation: Option<String>,
699 pub span: Span,
700 pub trivia: Trivia,
701}
702
703/// An agent step invariant (v0.116 §, testing track slice 4). A named predicate
704/// over the *pair* of committed states — the pre-commit `old` and the proposed
705/// `new`, each the agent's state record — that must hold of every state move; a
706/// commit that would violate it faults (`InvariantViolation`) before the state is
707/// persisted, exactly as a snapshot [`Invariant`] does. Widens the invariant
708/// subject from a snapshot to a step (ADR 0144 — one predicate surface); the
709/// predicate reuses the invariant surface (`implies`/`is`/pure methods) with
710/// `old`/`new` bound contextually (`old.status is Paid implies new.status is
711/// Paid`).
712#[derive(Debug, Clone)]
713pub struct Transition {
714 pub name: Ident,
715 /// The predicate expression — an ordinary `Bool`-typed expression over the
716 /// `old` and `new` state records, with `implies`/`is` and pure methods,
717 /// mirroring [`Invariant`].
718 pub predicate: Expr,
719 pub documentation: Option<String>,
720 pub span: Span,
721 pub trivia: Trivia,
722}
723
724/// A function contract clause (v0.115 §, testing track slice 3). A named
725/// predicate on a `fn` signature — a `requires` (precondition) or `ensures`
726/// (postcondition). A contract is the invariant predicate attached to a
727/// function (ADR 0144 — one predicate surface): the predicate is a pure `Bool`
728/// expression over the parameters (`requires`) or the parameters plus `result`
729/// (`ensures`), with `implies`/`is` and pure methods, mirroring [`Invariant`].
730/// The name rides the failure report and the redundant-test dedup.
731#[derive(Debug, Clone)]
732pub struct Contract {
733 pub name: Ident,
734 /// The predicate expression — an ordinary `Bool`-typed expression over the
735 /// parameters (and, for an `ensures`, the contextual `result` binding).
736 pub predicate: Expr,
737 pub span: Span,
738}
739
740/// An actor declaration (v0.45 §3.7). An actor is a nominal *contract type*
741/// describing an external party at a boundary — not a runnable entity. A
742/// handler consumes an actor on its `by` clause; the boundary verifies the
743/// declared `auth` scheme and mints a sealed identity (`name.identity`).
744#[derive(Debug, Clone)]
745pub struct ActorDecl {
746 pub name: Ident,
747 /// The authentication scheme from `auth = <Scheme>`, stored as the raw
748 /// identifier. The checker classifies it: `None`/`Internal`/`Bearer` are
749 /// admitted; `Signature` is reserved-and-rejected
750 /// (`bynk.actor.scheme_unsupported`); anything else is
751 /// `bynk.actor.unknown_scheme`. `None` for the refinement form.
752 pub auth: Option<Ident>,
753 /// The scheme's keyed config from `auth = Scheme(key = value, …)` (v0.47
754 /// `Bearer(secret = "…")`; v0.51 generalised for `Signature(secret, header,
755 /// timestamp?, tolerance?)`). Empty for schemes/forms with no config. The
756 /// checker validates which keys each scheme requires/allows.
757 pub auth_config: Vec<SchemeArg>,
758 /// The optional identity type from `, identity = <T>`. Absent ⇒ the
759 /// scheme default (`()` for `None`; a sealed `CallerId` for the `Internal`
760 /// `on call` channel, `()` for other `Internal` channels).
761 pub identity: Option<TypeRef>,
762 /// The reserved-and-rejected refinement form `actor Admin = Base where p`
763 /// (Q3). Parsed so the grammar is fixed now; the checker emits
764 /// `bynk.actor.refinement_unsupported`.
765 pub refinement: Option<ActorRefinement>,
766 pub documentation: Option<String>,
767 pub span: Span,
768 pub trivia: Trivia,
769}
770
771impl ActorDecl {
772 /// The value of a scheme config arg by key, if present (e.g. `secret`,
773 /// `header`).
774 pub fn scheme_arg(&self, key: &str) -> Option<&SchemeArg> {
775 self.auth_config.iter().find(|a| a.key.name == key)
776 }
777}
778
779/// One `key = value` argument in a scheme config (`Scheme(key = value, …)`).
780#[derive(Debug, Clone)]
781pub struct SchemeArg {
782 pub key: Ident,
783 pub value: SchemeArgValue,
784 /// Span of the value, for diagnostics.
785 pub span: Span,
786}
787
788/// A scheme config arg value — a string literal or an integer.
789#[derive(Debug, Clone)]
790pub enum SchemeArgValue {
791 Str(String),
792 Int(i64),
793}
794
795impl SchemeArgValue {
796 pub fn as_str(&self) -> Option<&str> {
797 match self {
798 SchemeArgValue::Str(s) => Some(s),
799 SchemeArgValue::Int(_) => None,
800 }
801 }
802 pub fn as_int(&self) -> Option<i64> {
803 match self {
804 SchemeArgValue::Int(n) => Some(*n),
805 SchemeArgValue::Str(_) => None,
806 }
807 }
808}
809
810/// The reserved refinement form `actor Admin = User where <predicate>` (Q3).
811/// Parsed in Foundations so the grammar is fixed; admission is a later slice.
812#[derive(Debug, Clone)]
813pub struct ActorRefinement {
814 /// The base actor being refined.
815 pub base: Ident,
816 /// The `where` predicate. Parsed but not yet checked.
817 pub predicate: Expr,
818 pub span: Span,
819}
820
821/// The `by (<binder>:)? <Actor>` clause on a handler (v0.45; binder optional in
822/// v0.50). Names the actor contract the handler consumes; when a `binder` is
823/// given, the verified identity binds to it and is read as `binder.identity`.
824/// Omitting the binder (`by <Actor>`) declares-and-verifies the contract without
825/// capturing the identity — for anonymous or verify-and-discard handlers. Sits
826/// after the protocol config and before the parameters.
827#[derive(Debug, Clone)]
828pub struct ByClause {
829 /// The identity binder, if the handler consumes the identity. `None` for the
830 /// binder-less `by <Actor>` form. Required when `actors` names more than one
831 /// (a sum is resolved by matching on the bound actor).
832 pub binder: Option<Ident>,
833 /// The actor contract(s) referenced — each a local actor decl or a prelude
834 /// actor. A single name is the ordinary single-actor handler; more than one
835 /// (`by who: A | B`, v0.52) is an **ordered sum of peer actors** resolved
836 /// first-wins, the body matching on the resolved actor. Always non-empty.
837 pub actors: Vec<Ident>,
838 pub span: Span,
839}
840
841impl ByClause {
842 /// The first (and, for a single-actor handler, only) actor contract named.
843 pub fn primary(&self) -> &Ident {
844 &self.actors[0]
845 }
846 /// Whether this `by` clause names an ordered sum of peer actors (`A | B`).
847 pub fn is_sum(&self) -> bool {
848 self.actors.len() > 1
849 }
850}
851
852/// A handler block — `on call(args) -> T given C1, C2 { body }`.
853/// Used by both services and agents.
854#[derive(Debug, Clone)]
855pub struct Handler {
856 pub kind: HandlerKind,
857 /// For agent handlers, the method-style handler name (e.g.
858 /// `on call addItem(...)`). For service handlers, this is None (just
859 /// `on call(...)`).
860 pub method_name: Option<Ident>,
861 /// The `by <binder>: <Actor>` clause (v0.45), if present. Service handlers
862 /// only; an absent clause inherits the protocol's default actor.
863 pub by_clause: Option<ByClause>,
864 pub params: Vec<Param>,
865 pub return_type: TypeRef,
866 pub given: Vec<CapRef>,
867 pub body: Block,
868 pub documentation: Option<String>,
869 pub span: Span,
870 pub trivia: Trivia,
871}
872
873#[derive(Debug, Clone, PartialEq, Eq)]
874pub enum HandlerKind {
875 /// `on call(...)` — typed RPC (the only kind in v0.5).
876 Call,
877 /// `on http METHOD "path"` — external-facing HTTP route (v0.9).
878 Http { method: HttpMethod, path: String },
879 /// `on cron "expr"` — scheduled task; `expr` is a 5-field cron
880 /// expression (v0.10a).
881 Cron { expr: String },
882 /// `on message(m: T)` — a message off the service's bound queue. The queue
883 /// binding lives on the service's `ServiceProtocol::Queue` (v0.44).
884 Message,
885 /// `on open ...` — the WebSocket upgrade handler (v0.103, real-time track
886 /// slice 3). Exactly one per `from WebSocket` service; carries a mandatory
887 /// `by` clause (edge auth) and receives a fresh owned `Connection[out]`.
888 Open,
889 /// `on close ...` — the WebSocket close handler (v0.106, real-time track slice
890 /// 3b-iii). Optional, ≤1 per `from WebSocket` service; runs when the socket
891 /// closes. Like `on open`, edge-authenticated (`by`), with the identity/params
892 /// recovered from the socket attachment (set at `on open`). (A `from WebSocket`
893 /// `on message` reuses [`HandlerKind::Message`], disambiguated by the protocol.)
894 Close,
895}
896
897/// HTTP methods supported by `on http` handlers (v0.9).
898#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
899pub enum HttpMethod {
900 Get,
901 Post,
902 Put,
903 Patch,
904 Delete,
905}
906
907impl HttpMethod {
908 pub fn as_str(self) -> &'static str {
909 match self {
910 HttpMethod::Get => "GET",
911 HttpMethod::Post => "POST",
912 HttpMethod::Put => "PUT",
913 HttpMethod::Patch => "PATCH",
914 HttpMethod::Delete => "DELETE",
915 }
916 }
917
918 pub fn from_ident(s: &str) -> Option<HttpMethod> {
919 match s {
920 "GET" => Some(HttpMethod::Get),
921 "POST" => Some(HttpMethod::Post),
922 "PUT" => Some(HttpMethod::Put),
923 "PATCH" => Some(HttpMethod::Patch),
924 "DELETE" => Some(HttpMethod::Delete),
925 _ => None,
926 }
927 }
928
929 /// True if this method conventionally has no request body.
930 pub fn forbids_body(self) -> bool {
931 matches!(self, HttpMethod::Get | HttpMethod::Delete)
932 }
933}
934
935/// Payload shape of an `HttpResult[T]` variant (v0.9 §3.3).
936#[derive(Debug, Clone, Copy, PartialEq, Eq)]
937pub enum HttpVariantPayload {
938 /// No payload (e.g. `NoContent`, `Unauthorized`).
939 None,
940 /// Carries a value of the `HttpResult` type parameter `T`.
941 Value,
942 /// Carries a `String` message (e.g. `BadRequest`, `Conflict`).
943 Message,
944 /// Carries a `String` target URL, emitted as a `Location` header — the
945 /// redirect variants (`Found`, `SeeOther`, `PermanentRedirect`, …).
946 Location,
947 /// Carries a `Stream[String]`, emitted as an SSE (`text/event-stream`)
948 /// streaming body — the `Streaming` (200) variant (v0.101, real-time track
949 /// slice 1).
950 Streamed,
951 /// Carries `(body: Bytes, contentType: String)` — the author-owned raw body
952 /// written straight into the response with the declared `content-type` and
953 /// **no codec** (the typed-wire guarantee is deliberately off). The `Raw`
954 /// (200) variant (v0.111); the first two-argument payload shape.
955 Raw,
956}
957
958/// One variant of the built-in `HttpResult[T]` sum (v0.9 §3.3).
959#[derive(Debug, Clone, Copy)]
960pub struct HttpVariant {
961 pub name: &'static str,
962 pub payload: HttpVariantPayload,
963 pub status: u16,
964}
965
966/// All `HttpResult[T]` variants, in declaration order (ascending status). The
967/// vocabulary tracks the common, modern HTTP status codes (RFC 9110): success
968/// and created/accepted (`Value`), redirects carrying a `Location` URL, and
969/// the client/server failures that handlers routinely return (`Message` when
970/// an explanation helps the caller, `None` for self-describing statuses).
971pub const HTTP_VARIANTS: &[HttpVariant] = &[
972 // ── 2xx success ──────────────────────────────────────────────────────
973 HttpVariant {
974 name: "Ok",
975 payload: HttpVariantPayload::Value,
976 status: 200,
977 },
978 // v0.101 (real-time track slice 1): a 200 whose body is a streamed
979 // `Stream[String]`, SSE-framed. Status precedes the body, so streaming is
980 // 200-only — pre-stream failures are ordinary variants returned instead.
981 HttpVariant {
982 name: "Streaming",
983 payload: HttpVariantPayload::Streamed,
984 status: 200,
985 },
986 // v0.111: a 200 whose body is an author-owned `Bytes` written straight into
987 // the response with the declared `content-type` — no codec runs. 200-only,
988 // like `Streaming`: it serves service-tier raw bodies (`robots.txt`,
989 // `sitemap.xml`, feeds, a QR PNG), not custom-status error pages.
990 HttpVariant {
991 name: "Raw",
992 payload: HttpVariantPayload::Raw,
993 status: 200,
994 },
995 HttpVariant {
996 name: "Created",
997 payload: HttpVariantPayload::Value,
998 status: 201,
999 },
1000 HttpVariant {
1001 name: "Accepted",
1002 payload: HttpVariantPayload::Value,
1003 status: 202,
1004 },
1005 HttpVariant {
1006 name: "NoContent",
1007 payload: HttpVariantPayload::None,
1008 status: 204,
1009 },
1010 // ── 3xx redirection (carry a `Location` URL) ─────────────────────────
1011 HttpVariant {
1012 name: "MovedPermanently",
1013 payload: HttpVariantPayload::Location,
1014 status: 301,
1015 },
1016 HttpVariant {
1017 name: "Found",
1018 payload: HttpVariantPayload::Location,
1019 status: 302,
1020 },
1021 HttpVariant {
1022 name: "SeeOther",
1023 payload: HttpVariantPayload::Location,
1024 status: 303,
1025 },
1026 HttpVariant {
1027 name: "TemporaryRedirect",
1028 payload: HttpVariantPayload::Location,
1029 status: 307,
1030 },
1031 HttpVariant {
1032 name: "PermanentRedirect",
1033 payload: HttpVariantPayload::Location,
1034 status: 308,
1035 },
1036 // ── 4xx client error ─────────────────────────────────────────────────
1037 HttpVariant {
1038 name: "BadRequest",
1039 payload: HttpVariantPayload::Message,
1040 status: 400,
1041 },
1042 HttpVariant {
1043 name: "Unauthorized",
1044 payload: HttpVariantPayload::None,
1045 status: 401,
1046 },
1047 HttpVariant {
1048 name: "Forbidden",
1049 payload: HttpVariantPayload::None,
1050 status: 403,
1051 },
1052 HttpVariant {
1053 name: "NotFound",
1054 payload: HttpVariantPayload::None,
1055 status: 404,
1056 },
1057 HttpVariant {
1058 name: "MethodNotAllowed",
1059 payload: HttpVariantPayload::None,
1060 status: 405,
1061 },
1062 HttpVariant {
1063 name: "NotAcceptable",
1064 payload: HttpVariantPayload::None,
1065 status: 406,
1066 },
1067 HttpVariant {
1068 name: "RequestTimeout",
1069 payload: HttpVariantPayload::None,
1070 status: 408,
1071 },
1072 HttpVariant {
1073 name: "Conflict",
1074 payload: HttpVariantPayload::Message,
1075 status: 409,
1076 },
1077 HttpVariant {
1078 name: "Gone",
1079 payload: HttpVariantPayload::None,
1080 status: 410,
1081 },
1082 HttpVariant {
1083 name: "LengthRequired",
1084 payload: HttpVariantPayload::None,
1085 status: 411,
1086 },
1087 HttpVariant {
1088 name: "PayloadTooLarge",
1089 payload: HttpVariantPayload::Message,
1090 status: 413,
1091 },
1092 HttpVariant {
1093 name: "UnsupportedMediaType",
1094 payload: HttpVariantPayload::Message,
1095 status: 415,
1096 },
1097 HttpVariant {
1098 name: "UnprocessableEntity",
1099 payload: HttpVariantPayload::Message,
1100 status: 422,
1101 },
1102 HttpVariant {
1103 name: "TooManyRequests",
1104 payload: HttpVariantPayload::Message,
1105 status: 429,
1106 },
1107 HttpVariant {
1108 name: "UnavailableForLegalReasons",
1109 payload: HttpVariantPayload::Message,
1110 status: 451,
1111 },
1112 // ── 5xx server error ─────────────────────────────────────────────────
1113 HttpVariant {
1114 name: "ServerError",
1115 payload: HttpVariantPayload::Message,
1116 status: 500,
1117 },
1118 HttpVariant {
1119 name: "NotImplemented",
1120 payload: HttpVariantPayload::Message,
1121 status: 501,
1122 },
1123 HttpVariant {
1124 name: "BadGateway",
1125 payload: HttpVariantPayload::Message,
1126 status: 502,
1127 },
1128 HttpVariant {
1129 name: "ServiceUnavailable",
1130 payload: HttpVariantPayload::Message,
1131 status: 503,
1132 },
1133 HttpVariant {
1134 name: "GatewayTimeout",
1135 payload: HttpVariantPayload::Message,
1136 status: 504,
1137 },
1138];
1139
1140/// Find an `HttpResult[T]` variant by name. Returns the variant info or
1141/// `None` if the name doesn't match.
1142pub fn http_variant(name: &str) -> Option<HttpVariant> {
1143 HTTP_VARIANTS.iter().copied().find(|v| v.name == name)
1144}
1145
1146/// Payload shape of a `QueueResult` variant (v0.44). Non-generic — a verdict
1147/// carries no value; `Retry` carries a `String` reason for the log path.
1148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1149pub enum QueueVariantPayload {
1150 /// No payload (`Ack`).
1151 None,
1152 /// Carries a `String` reason (`Retry`).
1153 Message,
1154}
1155
1156/// One variant of the built-in `QueueResult` sum (v0.44).
1157#[derive(Debug, Clone, Copy)]
1158pub struct QueueVariant {
1159 pub name: &'static str,
1160 pub payload: QueueVariantPayload,
1161}
1162
1163/// All `QueueResult` variants, in declaration order. `Ack` confirms the
1164/// message; `Retry` redelivers it, carrying a reason for observability.
1165pub const QUEUE_VARIANTS: &[QueueVariant] = &[
1166 QueueVariant {
1167 name: "Ack",
1168 payload: QueueVariantPayload::None,
1169 },
1170 QueueVariant {
1171 name: "Retry",
1172 payload: QueueVariantPayload::Message,
1173 },
1174];
1175
1176/// Find a `QueueResult` variant by name.
1177pub fn queue_variant(name: &str) -> Option<QueueVariant> {
1178 QUEUE_VARIANTS.iter().copied().find(|v| v.name == name)
1179}
1180
1181#[derive(Debug, Clone)]
1182pub struct TypeDecl {
1183 pub name: Ident,
1184 pub body: TypeBody,
1185 /// Documentation block attached to this declaration (v0.3).
1186 pub documentation: Option<String>,
1187 pub span: Span,
1188 pub trivia: Trivia,
1189}
1190
1191/// The right-hand side of a `type` declaration. In v0/v0.1 only the
1192/// `Refined` variant existed; v0.2 adds records and sums; v0.3 adds opaque.
1193#[derive(Debug, Clone)]
1194pub enum TypeBody {
1195 /// Refined base type: `BaseType where refinement`.
1196 Refined {
1197 base: BaseType,
1198 base_span: Span,
1199 refinement: Option<Refinement>,
1200 },
1201 /// Record type: `{ field: T where ..., ... }`.
1202 Record(RecordBody),
1203 /// Sum type: pipe-form variants or `enum { ... }` shorthand.
1204 Sum(SumBody),
1205 /// Opaque base type: `opaque BaseType (where refinement)?` (v0.3 §3.4).
1206 /// Identity is nominal; the base type is hidden outside the defining commons.
1207 Opaque {
1208 base: BaseType,
1209 base_span: Span,
1210 refinement: Option<Refinement>,
1211 },
1212}
1213
1214/// Body of a record-type declaration (v0.2 §3.1).
1215#[derive(Debug, Clone)]
1216pub struct RecordBody {
1217 pub fields: Vec<RecordField>,
1218 pub span: Span,
1219}
1220
1221/// One field of a record type declaration. Each field may carry inline
1222/// refinement, which is enforced at construction time on the field's value.
1223#[derive(Debug, Clone)]
1224pub struct RecordField {
1225 pub name: Ident,
1226 pub type_ref: TypeRef,
1227 pub refinement: Option<Refinement>,
1228 /// v0.11: an optional initial-value expression. Only meaningful on agent
1229 /// `state` fields (the field's fresh-key value); ignored / rejected on
1230 /// record-type fields by the checker.
1231 pub init: Option<Expr>,
1232 pub span: Span,
1233}
1234
1235/// Body of a sum-type declaration (v0.2 §3.2).
1236#[derive(Debug, Clone)]
1237pub struct SumBody {
1238 pub variants: Vec<Variant>,
1239 pub span: Span,
1240}
1241
1242/// One variant of a sum type. Variants may have payload fields; a
1243/// payload-less variant is a simple tag.
1244#[derive(Debug, Clone)]
1245pub struct Variant {
1246 pub name: Ident,
1247 pub payload: Vec<VariantField>,
1248 pub span: Span,
1249}
1250
1251/// One payload field of a sum variant. Variant payload fields use named
1252/// declarations like record fields, but do not carry refinement in v0.2.
1253#[derive(Debug, Clone)]
1254pub struct VariantField {
1255 pub name: Ident,
1256 pub type_ref: TypeRef,
1257 pub span: Span,
1258}
1259
1260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1261pub enum BaseType {
1262 Int,
1263 String,
1264 Bool,
1265 Float,
1266 /// `Duration` (v0.86, ADR 0112) — a span of time, a distinct base type
1267 /// erased to TS `number` carrying milliseconds (the `Clock` unit). Modelled
1268 /// on `Float`: Bynk-side-only, no implicit `Int` coercion (save the one
1269 /// sanctioned clock-math mix).
1270 Duration,
1271 /// `Instant` (v0.90, ADR 0114) — an absolute point in time, a distinct base
1272 /// type erased to TS `number` carrying Unix epoch milliseconds (the
1273 /// `Clock` unit). No literal (minted by `Clock.now()`); arithmetic composes
1274 /// with `Duration` (`Instant ± Duration -> Instant`, `Instant − Instant ->
1275 /// Duration`). Supersedes ADR 0112 D4's `Int`↔`Duration` clock-math mix.
1276 Instant,
1277 /// `Bytes` (v0.110, ADR 0142) — an immutable finite octet sequence, the
1278 /// seventh base type. Unlike its neighbours it does **not** erase to TS
1279 /// `number`: a `Bytes` lowers to a `Uint8Array`. No source literal
1280 /// (constructed via `Bytes.fromUtf8`/`fromBase64`/`empty`); `==` compares
1281 /// by content (real emitter codegen, not host `===`); wires as a base64
1282 /// JSON string; not `Map`-keyable and not orderable.
1283 Bytes,
1284}
1285
1286impl BaseType {
1287 pub fn name(self) -> &'static str {
1288 match self {
1289 BaseType::Int => "Int",
1290 BaseType::String => "String",
1291 BaseType::Bool => "Bool",
1292 BaseType::Float => "Float",
1293 BaseType::Duration => "Duration",
1294 BaseType::Instant => "Instant",
1295 BaseType::Bytes => "Bytes",
1296 }
1297 }
1298}
1299
1300/// A `Duration` literal unit (v0.86, ADR 0112) — the closed set of suffixes in a
1301/// `<int>.<unit>` literal. Each maps to a fixed millisecond factor (`Duration`
1302/// erases to `Int` milliseconds).
1303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1304pub enum DurationUnit {
1305 Milliseconds,
1306 Seconds,
1307 Minutes,
1308 Hours,
1309 Days,
1310}
1311
1312impl DurationUnit {
1313 /// Resolve a unit name (`minutes`) to its variant, or `None` if it is not one
1314 /// of the closed set. Used by the parser to recognise an `<int>.<unit>`
1315 /// literal; an unrecognised name leaves the expression a field access.
1316 pub fn from_name(name: &str) -> Option<Self> {
1317 Some(match name {
1318 "milliseconds" => DurationUnit::Milliseconds,
1319 "seconds" => DurationUnit::Seconds,
1320 "minutes" => DurationUnit::Minutes,
1321 "hours" => DurationUnit::Hours,
1322 "days" => DurationUnit::Days,
1323 _ => return None,
1324 })
1325 }
1326
1327 /// The unit name as written.
1328 pub fn name(self) -> &'static str {
1329 match self {
1330 DurationUnit::Milliseconds => "milliseconds",
1331 DurationUnit::Seconds => "seconds",
1332 DurationUnit::Minutes => "minutes",
1333 DurationUnit::Hours => "hours",
1334 DurationUnit::Days => "days",
1335 }
1336 }
1337
1338 /// The unit's value in milliseconds.
1339 pub fn millis(self) -> i64 {
1340 match self {
1341 DurationUnit::Milliseconds => 1,
1342 DurationUnit::Seconds => 1_000,
1343 DurationUnit::Minutes => 60_000,
1344 DurationUnit::Hours => 3_600_000,
1345 DurationUnit::Days => 86_400_000,
1346 }
1347 }
1348}
1349
1350/// An integer refinement bound (v0.40, ADR 0073): the parsed value plus the
1351/// bound's source span (covering a leading `-`). Value-only beyond the span —
1352/// ints have one canonical printed form, so the formatter stays idempotent
1353/// without a stored lexeme. The span backs the `InRange`-swap quick-fix.
1354#[derive(Debug, Clone)]
1355pub struct IntBound {
1356 pub value: i64,
1357 pub span: Span,
1358}
1359
1360/// A float refinement bound (v0.21): the parsed value plus the signed source
1361/// lexeme (for byte-stable emission). v0.40 (ADR 0073): also the source span,
1362/// for the `InRange`-swap quick-fix.
1363#[derive(Debug, Clone)]
1364pub struct FloatBound {
1365 pub value: f64,
1366 pub lexeme: String,
1367 pub span: Span,
1368}
1369
1370#[derive(Debug, Clone)]
1371pub struct Refinement {
1372 pub predicates: Vec<RefinementPred>,
1373 pub span: Span,
1374}
1375
1376#[derive(Debug, Clone)]
1377pub struct RefinementPred {
1378 pub kind: PredKind,
1379 pub span: Span,
1380}
1381
1382#[derive(Debug, Clone)]
1383pub enum PredKind {
1384 Matches(String),
1385 InRange(IntBound, IntBound),
1386 /// `InRange` with float bounds (v0.21) — a separate variant so every
1387 /// `Int` refinement path stays untouched. Bounds keep their source
1388 /// lexemes (including any sign) so emitted runtime checks are
1389 /// byte-stable.
1390 InRangeF(FloatBound, FloatBound),
1391 MinLength(i64),
1392 MaxLength(i64),
1393 Length(i64),
1394 NonNegative,
1395 Positive,
1396 NonEmpty,
1397}
1398
1399impl PredKind {
1400 pub fn name(&self) -> &'static str {
1401 match self {
1402 PredKind::Matches(_) => "Matches",
1403 PredKind::InRange(..) | PredKind::InRangeF(..) => "InRange",
1404 PredKind::MinLength(_) => "MinLength",
1405 PredKind::MaxLength(_) => "MaxLength",
1406 PredKind::Length(_) => "Length",
1407 PredKind::NonNegative => "NonNegative",
1408 PredKind::Positive => "Positive",
1409 PredKind::NonEmpty => "NonEmpty",
1410 }
1411 }
1412}
1413
1414/// A function type parameter (v0.20a, `fn name[A, B](…)`). A struct rather
1415/// than a bare Ident so the ADR-0028 "bound-capable" promise is a later field
1416/// addition, not a representation change.
1417#[derive(Debug, Clone)]
1418pub struct TypeParam {
1419 pub name: Ident,
1420 pub span: Span,
1421}
1422
1423/// A lambda expression (v0.20a): `(params) => expr` or `(params) => { … }`.
1424/// `=>` is the value arrow (shared with `match`); param annotations are
1425/// optional where an expected function type supplies them.
1426#[derive(Debug, Clone)]
1427pub struct LambdaExpr {
1428 pub params: Vec<LambdaParam>,
1429 pub body: Box<Expr>,
1430 pub span: Span,
1431}
1432
1433/// A lambda parameter. A separate type from [`Param`] because its annotation
1434/// is optional — `Param.type_ref` stays mandatory at every signature site.
1435#[derive(Debug, Clone)]
1436pub struct LambdaParam {
1437 pub name: Ident,
1438 pub type_ref: Option<TypeRef>,
1439 pub span: Span,
1440}
1441
1442#[derive(Debug, Clone)]
1443pub struct FnDecl {
1444 /// v0.20a: `[A, B]` type parameters; empty for non-generic functions.
1445 pub type_params: Vec<TypeParam>,
1446 /// Free function or method (`TypeName.methodName`). See [`FnName`].
1447 pub name: FnName,
1448 pub params: Vec<Param>,
1449 pub return_type: TypeRef,
1450 /// v0.115: preconditions (`requires <name>: <pred>`), parsed between the
1451 /// return type and the body. A contract clause is the invariant predicate
1452 /// attached to a function (ADR 0144 — one predicate surface); `requires`
1453 /// scopes over the parameters only.
1454 pub requires: Vec<Contract>,
1455 /// v0.115: postconditions (`ensures <name>: <pred>`). Scopes over the
1456 /// parameters *and* `result`, the contextual binding for the return value.
1457 pub ensures: Vec<Contract>,
1458 pub body: Block,
1459 /// True when the first parameter is the special `self` parameter. Only
1460 /// valid for method declarations.
1461 pub has_self: bool,
1462 /// Documentation block attached to this declaration (v0.3).
1463 pub documentation: Option<String>,
1464 pub span: Span,
1465 pub trivia: Trivia,
1466}
1467
1468/// A function-declaration name: either a free function `f` or a method
1469/// `T.method` (v0.2 §3.6).
1470#[derive(Debug, Clone)]
1471pub enum FnName {
1472 /// `fn name(...)` — a free function.
1473 Free(Ident),
1474 /// `fn TypeName.methodName(...)` — a method attached to a type.
1475 Method {
1476 type_name: Ident,
1477 method_name: Ident,
1478 },
1479}
1480
1481impl FnName {
1482 /// The function's short name for diagnostics. For methods returns the
1483 /// method portion only; the type prefix is recovered via `type_name`.
1484 pub fn ident(&self) -> &Ident {
1485 match self {
1486 FnName::Free(id) => id,
1487 FnName::Method { method_name, .. } => method_name,
1488 }
1489 }
1490
1491 /// For methods, the attached type's identifier; `None` for free fns.
1492 pub fn type_name(&self) -> Option<&Ident> {
1493 match self {
1494 FnName::Free(_) => None,
1495 FnName::Method { type_name, .. } => Some(type_name),
1496 }
1497 }
1498
1499 /// The displayed full name (e.g., `Money.add` or `parseSku`).
1500 pub fn display(&self) -> String {
1501 match self {
1502 FnName::Free(id) => id.name.clone(),
1503 FnName::Method {
1504 type_name,
1505 method_name,
1506 } => format!("{}.{}", type_name.name, method_name.name),
1507 }
1508 }
1509}
1510
1511/// A brace-delimited block of statements ending in a tail expression
1512/// whose value is the block's value (spec v0.1 §3.1).
1513#[derive(Debug, Clone)]
1514pub struct Block {
1515 pub statements: Vec<Statement>,
1516 pub tail: Box<Expr>,
1517 pub span: Span,
1518 /// Line comments that appear between the last statement (or the
1519 /// opening brace) and the tail expression. Preserved here because
1520 /// expressions do not carry trivia in v1.1.
1521 pub tail_leading_comments: Vec<String>,
1522}
1523
1524/// Block-level statement.
1525#[derive(Debug, Clone)]
1526pub enum Statement {
1527 /// `let name (: T)? = expr` — pure binding (v0.1).
1528 Let(LetStmt),
1529 /// `let name (: T)? <- expr` — effectful binding (v0.5).
1530 EffectLet(LetStmt),
1531 /// `expect expr` — verify a Bool predicate at test runtime (v0.7; renamed
1532 /// from `assert` in v0.112). Only valid inside test case bodies.
1533 Expect(ExpectStmt),
1534 /// `~> expr` — an asynchronous fire-and-forget send (v0.79). The caller does
1535 /// not await the reply; legal only when the reply is `Effect[()]`. No binder.
1536 Send(SendStmt),
1537 /// `name := expr` — a `Cell` store write (v0.81, storage track). The
1538 /// unconditional write form; `.update(fn)` (a method call) is the
1539 /// read-modify-write form. ADR 0108.
1540 Assign(AssignStmt),
1541}
1542
1543impl Statement {
1544 pub fn span(&self) -> Span {
1545 match self {
1546 Statement::Let(l) | Statement::EffectLet(l) => l.span,
1547 Statement::Expect(a) => a.span,
1548 Statement::Send(s) => s.span,
1549 Statement::Assign(a) => a.span,
1550 }
1551 }
1552}
1553
1554#[derive(Debug, Clone)]
1555pub struct ExpectStmt {
1556 pub value: Expr,
1557 pub span: Span,
1558 pub trivia: Trivia,
1559}
1560
1561/// `name := expr` — a `Cell` store write (v0.81, storage track). `target` is the
1562/// `Cell` field being written (a bare name for now; the checker resolves it to a
1563/// `store` field). `value` is the new value.
1564#[derive(Debug, Clone)]
1565pub struct AssignStmt {
1566 pub target: Ident,
1567 pub value: Expr,
1568 pub span: Span,
1569 pub trivia: Trivia,
1570}
1571
1572#[derive(Debug, Clone)]
1573pub struct LetStmt {
1574 pub name: Ident,
1575 pub type_annot: Option<TypeRef>,
1576 pub value: Expr,
1577 pub span: Span,
1578 pub trivia: Trivia,
1579}
1580
1581#[derive(Debug, Clone)]
1582pub struct SendStmt {
1583 /// The send target — a recipient call, e.g. `Logger.info(msg)`.
1584 pub value: Expr,
1585 pub span: Span,
1586 pub trivia: Trivia,
1587}
1588
1589#[derive(Debug, Clone)]
1590pub struct Param {
1591 pub name: Ident,
1592 pub type_ref: TypeRef,
1593 pub span: Span,
1594}
1595
1596#[derive(Debug, Clone)]
1597pub enum TypeRef {
1598 Base(BaseType, Span),
1599 Named(Ident),
1600 /// `Result[T, E]` — the built-in generic Result type (v0.1).
1601 Result(Box<TypeRef>, Box<TypeRef>, Span),
1602 /// `Option[T]` — the built-in generic Option type (v0.2).
1603 Option(Box<TypeRef>, Span),
1604 /// `Effect[T]` — the built-in generic Effect type (v0.5).
1605 Effect(Box<TypeRef>, Span),
1606 /// `HttpResult[T]` — the built-in HTTP-result sum (v0.9).
1607 HttpResult(Box<TypeRef>, Span),
1608 /// `QueueResult` — the built-in queue verdict sum (`Ack | Retry`),
1609 /// non-generic; the required return of a queue handler (v0.44).
1610 QueueResult(Span),
1611 /// `List[T]` — the built-in generic immutable list type (v0.20b).
1612 List(Box<TypeRef>, Span),
1613 /// `Map[K, V]` — the built-in generic immutable map type (v0.20b).
1614 /// Keys are confined to value-keyable types
1615 /// (`bynk.types.unkeyable_map_key`).
1616 Map(Box<TypeRef>, Box<TypeRef>, Span),
1617 /// `Query[T]` — the built-in lazy storage-read description (v0.91, ADR 0115).
1618 /// Nameable in a pure helper's return type; non-storable and non-boundary
1619 /// (like `Effect`/`Fn`).
1620 Query(Box<TypeRef>, Span),
1621 /// `Stream[T]` — the value-over-time primitive (v0.100, real-time track
1622 /// slice 0). A lazy, pull-shaped sequence produced over time; non-storable
1623 /// and non-boundary (like `Query`/`Effect`/`Fn`).
1624 Stream(Box<TypeRef>, Span),
1625 /// `Connection[F]` — a held WebSocket connection (v0.102, real-time track
1626 /// slice 2). `F` is the server→client frame type. A `Held` resource:
1627 /// non-serialisable, non-boundary, and governed by the linearity discipline
1628 /// (§2.9); storable only in `Cell[Option[Connection]]` / `Map[K, Connection]`.
1629 Connection(Box<TypeRef>, Span),
1630 /// `History[Agent]` — a generated, driven call-history of an agent (v0.119,
1631 /// testing track slice 7, ADR 0155). A test-only generator, legal only in
1632 /// `for all` binding position inside a `property`; it is not a value type,
1633 /// so it never resolves in a field/param/return position. The bound subject
1634 /// behaves as an ordinary `List[Step]`.
1635 History(Box<TypeRef>, Span),
1636 /// `ValidationError` — the built-in error type used by refined-type
1637 /// constructors (v0.1).
1638 ValidationError(Span),
1639 /// `JsonError` — the built-in JSON-decode error type (v0.22b). A
1640 /// uniform record (`kind`/`path`/`message`, all `String`) the codec
1641 /// maps `BoundaryError` variants and parse failures into.
1642 JsonError(Span),
1643 /// `()` — the unit type (v0.5).
1644 Unit(Span),
1645 /// `A -> B` / `(A, B) -> C` / `() -> B` — a function type (v0.20a).
1646 /// Right-associative; effectful iff the return type is `Effect[_]`
1647 /// (the structural rule). Confined to non-boundary positions
1648 /// (`bynk.types.function_at_boundary`).
1649 Fn(Vec<TypeRef>, Box<TypeRef>, Span),
1650}
1651
1652impl TypeRef {
1653 pub fn span(&self) -> Span {
1654 match self {
1655 TypeRef::Base(_, s) => *s,
1656 TypeRef::Named(id) => id.span,
1657 TypeRef::Result(_, _, s) => *s,
1658 TypeRef::Option(_, s) => *s,
1659 TypeRef::Effect(_, s) => *s,
1660 TypeRef::HttpResult(_, s) => *s,
1661 TypeRef::QueueResult(s) => *s,
1662 TypeRef::List(_, s) => *s,
1663 TypeRef::Map(_, _, s) => *s,
1664 TypeRef::Query(_, s) => *s,
1665 TypeRef::Stream(_, s) => *s,
1666 TypeRef::Connection(_, s) => *s,
1667 TypeRef::History(_, s) => *s,
1668 TypeRef::ValidationError(s) => *s,
1669 TypeRef::JsonError(s) => *s,
1670 TypeRef::Unit(s) => *s,
1671 TypeRef::Fn(_, _, s) => *s,
1672 }
1673 }
1674}
1675
1676#[derive(Debug, Clone)]
1677pub struct Expr {
1678 pub kind: ExprKind,
1679 pub span: Span,
1680}
1681
1682#[derive(Debug, Clone)]
1683pub enum ExprKind {
1684 IntLit(i64),
1685 /// A float literal (v0.21). The lexeme is kept alongside the parsed
1686 /// value so emission and formatting are byte-stable (`1e10` must not
1687 /// normalise to `10000000000`).
1688 FloatLit {
1689 value: f64,
1690 lexeme: String,
1691 },
1692 /// A duration literal `<int>.<unit>` (v0.86, ADR 0112): `5.minutes`,
1693 /// `30.days`. The parser recognises the `IntLit . <unit>` shape and records
1694 /// the magnitude, the unit, and the resolved milliseconds (the value the
1695 /// emitter lowers to). Typed `Duration`.
1696 DurationLit {
1697 /// The integer magnitude as written (`5` in `5.minutes`).
1698 value: i64,
1699 /// The unit name (`minutes`), one of the closed set.
1700 unit: DurationUnit,
1701 /// The value in milliseconds — `value * unit factor`.
1702 millis: i64,
1703 },
1704 StrLit(String),
1705 /// An interpolated string `"… \(expr) …"` (v0.43, ADR 0075). Chunks and
1706 /// holes alternate. A plain `"…"` with no holes stays [`ExprKind::StrLit`],
1707 /// so existing code and the emitter/formatter fast-path are untouched.
1708 InterpStr(Vec<InterpPart>),
1709 BoolLit(bool),
1710 Ident(Ident),
1711 Call {
1712 name: Ident,
1713 /// v0.20a: explicit type arguments (`name[T](…)`); empty when absent.
1714 type_args: Vec<TypeRef>,
1715 args: Vec<Expr>,
1716 },
1717 /// A lambda (v0.20a). See [`LambdaExpr`].
1718 Lambda(LambdaExpr),
1719 BinOp(BinOp, Box<Expr>, Box<Expr>),
1720 UnaryOp(UnaryOp, Box<Expr>),
1721 Paren(Box<Expr>),
1722 /// `{ stmts; expr }` — block expression (v0.1).
1723 Block(Block),
1724 /// `if cond { then } else { else }` (v0.1).
1725 If {
1726 cond: Box<Expr>,
1727 then_block: Box<Block>,
1728 else_block: Box<Block>,
1729 },
1730 /// `Ok(value)` — Result success constructor (v0.1).
1731 Ok(Box<Expr>),
1732 /// `Err(error)` — Result failure constructor (v0.1).
1733 Err(Box<Expr>),
1734 /// `expr?` — propagation operator (v0.1).
1735 Question(Box<Expr>),
1736 /// `TypeName.method(args)` — qualified static call on a type
1737 /// (v0.1: only refined-type `of`; v0.2: any static method or variant
1738 /// constructor for sum types). The resolver decides which.
1739 ConstructorCall {
1740 type_name: Ident,
1741 method: Ident,
1742 args: Vec<Expr>,
1743 },
1744 /// `TypeName { field: value, ... }` — record construction (v0.2).
1745 RecordConstruction {
1746 type_name: Ident,
1747 fields: Vec<FieldInit>,
1748 },
1749 /// `receiver.field` — field access on a record value (v0.2). v0.3 adds
1750 /// `.raw` on opaque types within the defining commons.
1751 FieldAccess {
1752 receiver: Box<Expr>,
1753 field: Ident,
1754 },
1755 /// `receiver.method(args)` — instance method call (v0.2). The
1756 /// resolver determines the receiver's type and looks up the method.
1757 MethodCall {
1758 receiver: Box<Expr>,
1759 method: Ident,
1760 /// v0.22b: explicit type arguments on a qualified static
1761 /// (`Json.decode[T](…)`); empty when absent. The same-line-`[`
1762 /// rule applies as for `Call` type application (0039).
1763 type_args: Vec<TypeRef>,
1764 args: Vec<Expr>,
1765 },
1766 /// `match disc { arm+ }` — pattern matching (v0.2).
1767 Match {
1768 discriminant: Box<Expr>,
1769 arms: Vec<MatchArm>,
1770 },
1771 /// `expr is pattern` — pattern test, returns Bool (v0.2).
1772 Is {
1773 value: Box<Expr>,
1774 pattern: Pattern,
1775 },
1776 /// `Some(value)` — Option Some constructor (v0.2).
1777 Some(Box<Expr>),
1778 /// `None` — Option None constructor (v0.2).
1779 None,
1780 /// `()` — unit literal (v0.5).
1781 UnitLit,
1782 /// `TypeName { ...base, field: value, ... }` or `{ ...base, ... }` —
1783 /// record spread expression (v0.5).
1784 RecordSpread {
1785 /// Optional type prefix (`TypeName { ...base }`). Absent for the
1786 /// bare form used inside `commit`.
1787 type_name: Option<Ident>,
1788 /// The base record being spread.
1789 base: Box<Expr>,
1790 /// Field overrides (always full `name: value` form — never shorthand).
1791 overrides: Vec<FieldInit>,
1792 },
1793 /// `Effect.pure(value)` — wrap a synchronous value into `Effect[T]`
1794 /// (v0.5). Recognised in the parser as a special-form.
1795 EffectPure(Box<Expr>),
1796 /// `expect expr` — expectation as an expression of type `()` (v0.9.1;
1797 /// renamed from `assert` in v0.112). Valid only inside test bodies. Evaluates
1798 /// `expr` (must be Bool); if false, the surrounding test case fails.
1799 Expect(Box<Expr>),
1800 /// `Val[T]`, `Val[T](args)` — test-context value construction (v0.9.4).
1801 /// `args` is empty for the bare form and holds the pin arguments for
1802 /// `Val[T](...)`. The record-override form `Val[T] { ... }` is not yet
1803 /// parsed. Valid only inside test bodies; has type `T`.
1804 Val {
1805 type_ref: TypeRef,
1806 args: Vec<Expr>,
1807 },
1808 /// `[a, b, c]` — list literal (v0.20b). An empty `[]` requires an
1809 /// expected type (`bynk.types.uninferable_element_type`).
1810 ListLit(Vec<Expr>),
1811 /// An observation over a consumed capability's recorded calls (v0.117,
1812 /// testing track slice 5). The direct subject of an `expect` in a `case`
1813 /// body — `expect Cap.op called once with <pred>`, `expect Cap.op never
1814 /// called`, `expect A.op before B.op`. Types as `Bool` (the claim about the
1815 /// recorded trace), lowered to a boolean over the recorded log.
1816 Observation(ObservationExpr),
1817 /// `trace(Cap.op)` — the bound-trace escape hatch (v0.117, testing track
1818 /// slice 5). Yields the recorded calls of `Cap.op` as a `List[<CallRecord>]`
1819 /// (a synthetic record of the operation's parameters), asserted over with the
1820 /// ordinary value surface. Test-body-only, like [`ExprKind::Val`].
1821 Trace {
1822 cap: Ident,
1823 op: Ident,
1824 },
1825}
1826
1827/// An observation of a capability operation's recorded calls (v0.117, testing
1828/// track slice 5). `cap`/`op` name the seam (`Logger.log`); `matcher` is the
1829/// claim about the recorded calls.
1830#[derive(Debug, Clone)]
1831pub struct ObservationExpr {
1832 pub cap: Ident,
1833 pub op: Ident,
1834 pub matcher: ObservationMatcher,
1835}
1836
1837/// The claim an [`ObservationExpr`] makes about a seam's recorded calls (v0.117).
1838#[derive(Debug, Clone)]
1839pub enum ObservationMatcher {
1840 /// `called` [`once` | `<n> times`]? [`with` `<pred>`]?. `count` is `None`
1841 /// for a bare `called` (at least one); `Some(expr)` is the exact-count claim
1842 /// (a literal; `once` desugars to `1`). `with_pred` matches a call whose
1843 /// arguments (in scope by the operation's parameter names) satisfy it.
1844 Called {
1845 count: Option<Box<Expr>>,
1846 with_pred: Option<Box<Expr>>,
1847 },
1848 /// `never called` — zero calls.
1849 NeverCalled,
1850 /// `before Cap.op` — the first call of the subject precedes the first call
1851 /// of the named operation (both must have occurred).
1852 Before { cap: Ident, op: Ident },
1853}
1854
1855/// One part of an interpolated string (v0.43, ADR 0075). An
1856/// [`ExprKind::InterpStr`] holds an alternating run of these.
1857#[derive(Debug, Clone)]
1858pub enum InterpPart {
1859 /// Literal text between holes, with escapes already resolved.
1860 Chunk(String),
1861 /// An interpolated expression `\(expr)`. Type-checked by the hole rule
1862 /// (base scalars only; see the checker) and lowered into a template-
1863 /// literal `${…}` slot.
1864 Hole(Box<Expr>),
1865}
1866
1867/// One field-initialiser inside a record construction expression:
1868/// either `name: expr` or the shorthand `name` (which requires a binding
1869/// of the same name in scope and uses its value).
1870#[derive(Debug, Clone)]
1871pub struct FieldInit {
1872 pub name: Ident,
1873 /// `None` means shorthand — the field's value is the same-named binding.
1874 pub value: Option<Expr>,
1875 pub span: Span,
1876}
1877
1878/// One arm of a `match` expression: `pattern => body`.
1879#[derive(Debug, Clone)]
1880pub struct MatchArm {
1881 pub pattern: Pattern,
1882 pub body: MatchBody,
1883 pub span: Span,
1884}
1885
1886/// The right-hand side of a match arm — either a single expression or
1887/// a block.
1888#[derive(Debug, Clone)]
1889pub enum MatchBody {
1890 Expr(Expr),
1891 Block(Block),
1892}
1893
1894impl MatchBody {
1895 pub fn span(&self) -> Span {
1896 match self {
1897 MatchBody::Expr(e) => e.span,
1898 MatchBody::Block(b) => b.span,
1899 }
1900 }
1901}
1902
1903/// A pattern (v0.2 §3.8). Patterns appear in `match` arms and as the
1904/// right-hand side of the `is` operator.
1905#[derive(Debug, Clone)]
1906pub enum Pattern {
1907 /// `_` — matches any value, no bindings.
1908 Wildcard(Span),
1909 /// `Variant` or `Variant(bindings)` or `TypeName.Variant(bindings)`.
1910 Variant {
1911 /// Optional qualifier: `TypeName.Variant`.
1912 type_name: Option<Ident>,
1913 /// The variant name.
1914 variant: Ident,
1915 /// Payload bindings (empty for nullary variants).
1916 bindings: Vec<PatternBinding>,
1917 span: Span,
1918 },
1919}
1920
1921impl Pattern {
1922 pub fn span(&self) -> Span {
1923 match self {
1924 Pattern::Wildcard(s) => *s,
1925 Pattern::Variant { span, .. } => *span,
1926 }
1927 }
1928}
1929
1930/// A single binding inside a variant pattern. Two surface forms:
1931/// `name` (positional — bind the i-th payload field) and
1932/// `fieldName: bindName` (named — bind the named payload field).
1933/// Both forms also accept `_` as the bind name to discard.
1934#[derive(Debug, Clone)]
1935pub struct PatternBinding {
1936 /// Source form: positional or named.
1937 pub kind: PatternBindingKind,
1938 pub span: Span,
1939}
1940
1941#[derive(Debug, Clone)]
1942pub enum PatternBindingKind {
1943 /// `name` (or `_`): bind the payload field at this position to `name`.
1944 Positional { name: Ident },
1945 /// `field: name` (or `field: _`): bind the named payload field to `name`.
1946 Named { field: Ident, name: Ident },
1947}
1948
1949impl PatternBinding {
1950 /// The local name introduced by this binding (used for scope).
1951 /// `_` is a sentinel for "no binding"; callers should compare against it.
1952 pub fn local_name(&self) -> &Ident {
1953 match &self.kind {
1954 PatternBindingKind::Positional { name } => name,
1955 PatternBindingKind::Named { name, .. } => name,
1956 }
1957 }
1958
1959 pub fn is_wildcard(&self) -> bool {
1960 self.local_name().name == "_"
1961 }
1962}
1963
1964#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1965pub enum BinOp {
1966 /// `P implies Q` — logical implication (v0.80). Desugars to `!P || Q`; sits
1967 /// at the lowest precedence (below `||`). Reads directionally (P → Q).
1968 Implies,
1969 Or,
1970 And,
1971 Eq,
1972 NotEq,
1973 Lt,
1974 LtEq,
1975 Gt,
1976 GtEq,
1977 Add,
1978 Sub,
1979 Mul,
1980 Div,
1981}
1982
1983impl BinOp {
1984 pub fn name(self) -> &'static str {
1985 match self {
1986 BinOp::Implies => "implies",
1987 BinOp::Or => "||",
1988 BinOp::And => "&&",
1989 BinOp::Eq => "==",
1990 BinOp::NotEq => "!=",
1991 BinOp::Lt => "<",
1992 BinOp::LtEq => "<=",
1993 BinOp::Gt => ">",
1994 BinOp::GtEq => ">=",
1995 BinOp::Add => "+",
1996 BinOp::Sub => "-",
1997 BinOp::Mul => "*",
1998 BinOp::Div => "/",
1999 }
2000 }
2001}
2002
2003#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2004pub enum UnaryOp {
2005 Neg,
2006 Not,
2007}
2008
2009impl UnaryOp {
2010 pub fn name(self) -> &'static str {
2011 match self {
2012 UnaryOp::Neg => "-",
2013 UnaryOp::Not => "!",
2014 }
2015 }
2016}