Skip to main content

daml_parser/
ast.rs

1//! Typed, lossless parse tree produced by the recursive-descent parser
2//! (src/parse.rs).
3//!
4//! Declarations, expressions, patterns, and most parser DTOs carry both a
5//! 1-based source `Pos` for their first token and a byte `Span` for their
6//! full source extent. `Type` nodes carry spans only because downstream
7//! consumers slice type source text but do not currently need line/column
8//! anchors per type fragment. Downstream crates consume this tree directly:
9//! daml-fmt re-prints layout from the spans, and daml-lint lowers it onto its
10//! own rule-facing IR.
11//!
12//! Parser-created trees are the supported construction path. Public fields are
13//! exposed so tools can match the tree directly; vectors preserve source order,
14//! `pos` is the first token's position, and `span` is the half-open byte range
15//! covering the node's real source tokens.
16
17pub use crate::lexer::{ByteOffset, Identifier, ModuleName, Operator, Pos};
18
19/// Byte span of an AST node.
20///
21/// `[start, end)` into the original source, same basis as `Token::start`/
22/// `Token::end`. Covers every (non-virtual) token that belongs to the node —
23/// first token's `start` to last token's `end`.
24///
25/// Invariants the parser maintains (checked over the corpus by
26/// `render_from_ast`): a child's span is contained in its parent's span, and
27/// sibling spans are ordered and non-overlapping. Trivia (comments, blank
28/// lines) live *between* sibling spans.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
30pub struct Span {
31    /// Inclusive byte offset at which the node starts.
32    pub start: ByteOffset,
33    /// Exclusive byte offset at which the node ends.
34    pub end: ByteOffset,
35}
36
37impl Span {
38    /// Create a span from already-typed byte offsets.
39    ///
40    /// Use [`Self::from_usize`] only at parser/source-slicing boundaries where
41    /// raw byte offsets are being converted deliberately.
42    ///
43    /// ```compile_fail
44    /// use daml_parser::ast::Span;
45    ///
46    /// let _ = Span::new(1usize, 2usize);
47    /// ```
48    #[must_use]
49    pub const fn new(start: ByteOffset, end: ByteOffset) -> Self {
50        Self { start, end }
51    }
52
53    /// Convert raw byte offsets into a typed parser span.
54    #[must_use]
55    pub const fn from_usize(start: usize, end: usize) -> Self {
56        Self {
57            start: ByteOffset::new(start),
58            end: ByteOffset::new(end),
59        }
60    }
61
62    /// Raw start byte offset for source slicing and external interop.
63    #[must_use]
64    pub const fn start_usize(self) -> usize {
65        self.start.get()
66    }
67
68    /// Raw exclusive end byte offset for source slicing and external interop.
69    #[must_use]
70    pub const fn end_usize(self) -> usize {
71        self.end.get()
72    }
73
74    /// True when the span is well-formed (`start <= end`).
75    #[must_use]
76    pub const fn is_valid(&self) -> bool {
77        self.start.get() <= self.end.get()
78    }
79
80    /// True for a zero-width but still valid span.
81    #[must_use]
82    pub const fn is_empty(&self) -> bool {
83        self.start.get() == self.end.get()
84    }
85
86    #[must_use]
87    pub const fn range(&self) -> std::ops::Range<usize> {
88        self.start.get()..self.end.get()
89    }
90
91    #[must_use]
92    pub fn get<'a>(&self, source: &'a str) -> Option<&'a str> {
93        let start = self.start.get();
94        let end = self.end.get();
95        if start <= end
96            && end <= source.len()
97            && source.is_char_boundary(start)
98            && source.is_char_boundary(end)
99        {
100            Some(&source[start..end])
101        } else {
102            None
103        }
104    }
105
106    /// `self` fully contains `other`.
107    #[must_use]
108    pub const fn contains(&self, other: &Self) -> bool {
109        self.is_valid()
110            && other.is_valid()
111            && self.start.get() <= other.start.get()
112            && other.end.get() <= self.end.get()
113    }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
117#[non_exhaustive]
118pub enum LitKind {
119    /// Integer literal.
120    Int,
121    /// Decimal literal.
122    Decimal,
123    /// Text/string literal.
124    Text,
125    /// Character literal.
126    Char,
127}
128
129/// Import syntax style.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum ImportStyle {
133    /// Qualified import (`import qualified Foo.Bar`, `import Foo.Bar qualified`).
134    Qualified,
135    /// Unqualified import (`import Foo.Bar`, `import Foo.Bar as Baz`).
136    Unqualified,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
140#[non_exhaustive]
141pub enum FieldAssign {
142    /// Explicit record assignment: `field = expression`.
143    Assign {
144        /// Field being assigned.
145        name: Identifier,
146        /// Right-hand-side expression after `=`.
147        value: Expr,
148        /// Position of the field name.
149        pos: Pos,
150        /// Span of the whole `field = expression` assignment.
151        span: Span,
152    },
153    /// Record pun: `field`, meaning `field = field`.
154    Pun {
155        /// Punned field name.
156        name: Identifier,
157        /// Position of the field name.
158        pos: Pos,
159        /// Span of the field name.
160        span: Span,
161    },
162    /// Record wildcard: `..`.
163    Wildcard {
164        /// Position of the `..` token.
165        pos: Pos,
166        /// Span of the `..` token.
167        span: Span,
168    },
169}
170
171impl FieldAssign {
172    #[must_use]
173    pub const fn pos(&self) -> Pos {
174        match self {
175            Self::Assign { pos, .. } | Self::Pun { pos, .. } | Self::Wildcard { pos, .. } => *pos,
176        }
177    }
178
179    #[must_use]
180    pub const fn span(&self) -> Span {
181        match self {
182            Self::Assign { span, .. } | Self::Pun { span, .. } | Self::Wildcard { span, .. } => {
183                *span
184            }
185        }
186    }
187
188    #[must_use]
189    pub const fn name(&self) -> Option<&Identifier> {
190        match self {
191            Self::Assign { name, .. } | Self::Pun { name, .. } => Some(name),
192            Self::Wildcard { .. } => None,
193        }
194    }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct Alt {
199    /// Pattern to match before `->`.
200    pub pat: Pat,
201    /// Alternative body after `->`.
202    pub body: Expr,
203    /// Position of the alternative's first token.
204    pub pos: Pos,
205    /// Span of the whole alternative.
206    pub span: Span,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct Binding {
211    /// Left-hand side: a variable with parameters, or a destructuring pattern.
212    pub pat: Pat,
213    /// Parameter patterns when the LHS is a function binding (`f x y = ...`).
214    pub params: Vec<Pat>,
215    /// Right-hand-side expression.
216    pub expr: Expr,
217    /// Position of the binding's first token.
218    pub pos: Pos,
219    /// Span of the whole binding.
220    pub span: Span,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
224#[non_exhaustive]
225pub enum Pat {
226    /// Variable pattern.
227    Var {
228        /// Bound variable name.
229        name: Identifier,
230        /// Position of the variable token.
231        pos: Pos,
232        /// Span of the variable token.
233        span: Span,
234    },
235    /// Wildcard pattern (`_`).
236    Wild {
237        /// Position of the `_` token.
238        pos: Pos,
239        /// Span of the `_` token.
240        span: Span,
241    },
242    /// Constructor pattern with source-ordered arguments.
243    Con {
244        /// Optional module qualifier before the constructor name.
245        qualifier: Option<ModuleName>,
246        /// Constructor name.
247        name: Identifier,
248        /// Constructor arguments in source order.
249        args: Vec<Self>,
250        /// Position of the constructor token.
251        pos: Pos,
252        /// Span of the whole constructor pattern.
253        span: Span,
254    },
255    /// Tuple pattern with source-ordered items.
256    Tuple {
257        /// Tuple items in source order.
258        items: Vec<Self>,
259        /// Position of the opening parenthesis.
260        pos: Pos,
261        /// Span from `(` through `)`.
262        span: Span,
263    },
264    /// List pattern with source-ordered items.
265    List {
266        /// List items in source order.
267        items: Vec<Self>,
268        /// Position of the opening bracket.
269        pos: Pos,
270        /// Span from `[` through `]`.
271        span: Span,
272    },
273    /// Literal pattern; `text` is the parser's normalized literal text.
274    Lit {
275        /// Literal family.
276        kind: LitKind,
277        /// Normalized literal payload.
278        text: String,
279        /// Position of the literal token.
280        pos: Pos,
281        /// Span of the literal token in the source.
282        span: Span,
283    },
284    /// `name@pat`
285    As {
286        /// Name bound to the whole matched pattern.
287        name: Identifier,
288        /// Pattern being aliased.
289        pat: Box<Self>,
290        /// Position of the bound name.
291        pos: Pos,
292        /// Span of the whole `name@pat` pattern.
293        span: Span,
294    },
295    /// Anything the parser couldn't classify; raw text preserved.
296    Other {
297        /// Raw source text of the unclassified pattern.
298        raw: String,
299        /// Position of the raw pattern's first token.
300        pos: Pos,
301        /// Span of the preserved raw pattern text.
302        span: Span,
303    },
304}
305
306#[derive(Debug, Clone, PartialEq, Eq)]
307#[non_exhaustive]
308pub enum Expr {
309    /// Lowercase variable reference, possibly qualified.
310    Var {
311        /// Optional module qualifier before the variable name.
312        qualifier: Option<ModuleName>,
313        /// Variable name.
314        name: Identifier,
315        /// Position of the variable token.
316        pos: Pos,
317        /// Span of the variable token.
318        span: Span,
319    },
320    /// Constructor / data-constructor reference, possibly qualified.
321    Con {
322        /// Optional module qualifier before the constructor name.
323        qualifier: Option<ModuleName>,
324        /// Constructor name.
325        name: Identifier,
326        /// Position of the constructor token.
327        pos: Pos,
328        /// Span of the constructor token.
329        span: Span,
330    },
331    /// Literal expression; `text` is the parser's normalized literal text.
332    Lit {
333        /// Literal family.
334        kind: LitKind,
335        /// Normalized literal payload.
336        text: String,
337        /// Position of the literal token.
338        pos: Pos,
339        /// Span of the literal token in the source.
340        span: Span,
341    },
342    /// Application, flattened: `f a b c` is one App with three args.
343    App {
344        /// Function expression being applied.
345        func: Box<Self>,
346        /// Arguments in source order.
347        args: Vec<Self>,
348        /// Position of the application's first token.
349        pos: Pos,
350        /// Span of the whole application.
351        span: Span,
352    },
353    /// Binary operator application with source-level operator text.
354    BinOp {
355        /// Operator token text.
356        op: Operator,
357        /// Left operand.
358        lhs: Box<Self>,
359        /// Right operand.
360        rhs: Box<Self>,
361        /// Position of the left operand.
362        pos: Pos,
363        /// Span of the whole infix expression.
364        span: Span,
365    },
366    /// Unary negation.
367    Neg {
368        /// Negated expression.
369        expr: Box<Self>,
370        /// Position of the `-` token.
371        pos: Pos,
372        /// Span of the whole negated expression.
373        span: Span,
374    },
375    /// Lambda expression (`\params -> body`).
376    Lambda {
377        /// Parameter patterns in source order.
378        params: Vec<Pat>,
379        /// Lambda body.
380        body: Box<Self>,
381        /// Position of the lambda token.
382        pos: Pos,
383        /// Span of the whole lambda expression.
384        span: Span,
385    },
386    /// Conditional expression.
387    If {
388        /// Condition after `if`.
389        cond: Box<Self>,
390        /// Expression after `then`.
391        then_branch: Box<Self>,
392        /// Expression after `else`.
393        else_branch: Box<Self>,
394        /// Position of the `if` token.
395        pos: Pos,
396        /// Span of the whole conditional expression.
397        span: Span,
398    },
399    /// Case expression with source-ordered alternatives.
400    Case {
401        /// Scrutinee after `case`.
402        scrutinee: Box<Self>,
403        /// Alternatives in source order.
404        alts: Vec<Alt>,
405        /// Position of the `case` token.
406        pos: Pos,
407        /// Span of the whole case expression.
408        span: Span,
409    },
410    /// Do block.
411    Do {
412        /// Statements in source order.
413        stmts: Vec<DoStmt>,
414        /// Position of the `do` token.
415        pos: Pos,
416        /// Span of the whole do block.
417        span: Span,
418    },
419    /// Let/in expression.
420    LetIn {
421        /// Bindings in source order.
422        bindings: Vec<Binding>,
423        /// Body expression after `in`.
424        body: Box<Self>,
425        /// Position of the `let` token.
426        pos: Pos,
427        /// Span of the whole let/in expression.
428        span: Span,
429    },
430    /// `base with f = e, ...` — record construction when base is a Con,
431    /// record update otherwise.
432    Record {
433        /// Constructor or record value being constructed/updated.
434        base: Box<Self>,
435        /// Field assignments in source order.
436        fields: Vec<FieldAssign>,
437        /// Position of the base expression.
438        pos: Pos,
439        /// Span of the whole record expression.
440        span: Span,
441    },
442    /// Tuple expression with source-ordered items.
443    Tuple {
444        /// Tuple items in source order.
445        items: Vec<Self>,
446        /// Position of the opening parenthesis.
447        pos: Pos,
448        /// Span from `(` through `)`.
449        span: Span,
450    },
451    /// List expression with source-ordered items.
452    List {
453        /// List items in source order.
454        items: Vec<Self>,
455        /// Position of the opening bracket.
456        pos: Pos,
457        /// Span from `[` through `]`.
458        span: Span,
459    },
460    /// `try <body> catch <alts>`
461    Try {
462        /// Body after `try`.
463        body: Box<Self>,
464        /// Catch handlers in source order.
465        handlers: Vec<Alt>,
466        /// Position of the `try` token.
467        pos: Pos,
468        /// Span of the whole try/catch expression.
469        span: Span,
470    },
471    /// Parenthesized operator reference like `(+)`.
472    OperatorRef {
473        /// Referenced operator.
474        op: Operator,
475        /// Position of the opening parenthesis.
476        pos: Pos,
477        /// Span from `(` through `)`.
478        span: Span,
479    },
480    /// Left operator section like `(1 +)`.
481    LeftSection {
482        /// Section operator.
483        op: Operator,
484        /// Left operand before the operator.
485        operand: Box<Self>,
486        /// Position of the opening parenthesis.
487        pos: Pos,
488        /// Span from `(` through `)`.
489        span: Span,
490    },
491    /// Right operator section like `(+ 1)`.
492    RightSection {
493        /// Section operator.
494        op: Operator,
495        /// Right operand after the operator.
496        operand: Box<Self>,
497        /// Position of the opening parenthesis.
498        pos: Pos,
499        /// Span from `(` through `)`.
500        span: Span,
501    },
502    /// Expression the parser could not understand; raw text preserved so
503    /// a parse failure degrades to the shim's behavior instead of dying.
504    Error {
505        /// Raw source text preserved for the malformed expression.
506        raw: String,
507        /// Position of the malformed expression's first token.
508        pos: Pos,
509        /// Span of the preserved raw expression text.
510        span: Span,
511    },
512}
513
514#[derive(Debug, Clone, PartialEq, Eq)]
515#[non_exhaustive]
516pub enum DoStmt {
517    /// `pat <- expr`
518    Bind {
519        /// Pattern before `<-`.
520        pat: Pat,
521        /// Expression after `<-`.
522        expr: Expr,
523        /// Position of the statement's first token.
524        pos: Pos,
525        /// Span of the whole bind statement.
526        span: Span,
527    },
528    /// `let x = e` (no `in`) inside a do block.
529    Let {
530        /// Let bindings in source order.
531        bindings: Vec<Binding>,
532        /// Position of the `let` token.
533        pos: Pos,
534        /// Span of the whole let statement.
535        span: Span,
536    },
537    /// Bare expression statement.
538    Expr {
539        /// Statement expression.
540        expr: Expr,
541        /// Position of the expression's first token.
542        pos: Pos,
543        /// Span of the expression statement.
544        span: Span,
545    },
546}
547
548/// Structured Daml type, parsed from the real token stream.
549///
550/// Scoped to the forms the corpus actually contains; it exists so consumers can
551/// tell a type *application* from a *function arrow* from an
552/// atomic constructor — a distinction a string matcher structurally cannot make.
553/// Every node carries a byte span so consumers can render exact source text from
554/// `(source, span)`. Unlike declarations, expressions, and patterns, type nodes
555/// do not carry a separate [`Pos`]; use [`Type::span`] and source line mapping
556/// when a line/column anchor is required for a type fragment.
557#[derive(Debug, Clone)]
558#[non_exhaustive]
559pub enum Type {
560    /// Type constructor, possibly qualified: `Party`, `DA.Map.Map`.
561    Con {
562        /// Optional module qualifier before the constructor name.
563        qualifier: Option<ModuleName>,
564        /// Constructor name.
565        name: Identifier,
566        /// Span of the constructor token in the source.
567        span: Span,
568    },
569    /// Type application, head applied to one or more args: `ContractId Foo`,
570    /// `Map Text Int`, `Script ()`. Type-level nat literals (the `10` in
571    /// `Numeric 10`) are NOT types, so they are dropped from the arg list — a
572    /// `Numeric 10` collapses to the bare head `Con "Numeric"`.
573    App(Box<Self>, Vec<Self>, Span),
574    /// List type `[T]`.
575    List(Box<Self>, Span),
576    /// Tuple type `(a, b, ...)`.
577    Tuple(Vec<Self>, Span),
578    /// Function type `a -> b` (right-associative).
579    Fun(Box<Self>, Box<Self>, Span),
580    /// Lowercase type variable: `a`, `n`.
581    Var(Identifier, Span),
582    /// The unit type `()`.
583    Unit(Span),
584    /// A constrained type `C a => T`: the context is not modeled, the body `T`
585    /// is kept.
586    Constrained(Box<Self>, Span),
587    /// Type-level string or char literal, e.g. the `"observers"` in
588    /// `HasField "observers" t PartiesMap`.
589    Lit {
590        /// Literal family.
591        kind: LitKind,
592        /// Normalized literal payload.
593        text: String,
594        /// Span of the literal token in the source.
595        span: Span,
596    },
597}
598
599/// Type equality intentionally ignores source spans.
600///
601/// Spans describe where equivalent type syntax appeared in a source file; they
602/// are not part of structural type identity used by parser consumers.
603impl PartialEq for Type {
604    fn eq(&self, other: &Self) -> bool {
605        match (self, other) {
606            (
607                Self::Con {
608                    qualifier: aq,
609                    name: an,
610                    ..
611                },
612                Self::Con {
613                    qualifier: bq,
614                    name: bn,
615                    ..
616                },
617            ) => aq == bq && an == bn,
618            (Self::App(ah, aa, _), Self::App(bh, ba, _)) => ah == bh && aa == ba,
619            (Self::List(a, _), Self::List(b, _))
620            | (Self::Constrained(a, _), Self::Constrained(b, _)) => a == b,
621            (Self::Tuple(a, _), Self::Tuple(b, _)) => a == b,
622            (Self::Fun(al, ar, _), Self::Fun(bl, br, _)) => al == bl && ar == br,
623            (Self::Var(a, _), Self::Var(b, _)) => a == b,
624            (Self::Unit(_), Self::Unit(_)) => true,
625            (
626                Self::Lit {
627                    kind: ak, text: at, ..
628                },
629                Self::Lit {
630                    kind: bk, text: bt, ..
631                },
632            ) => ak == bk && at == bt,
633            _ => false,
634        }
635    }
636}
637
638impl Eq for Type {}
639
640impl Type {
641    #[must_use]
642    pub const fn span(&self) -> Span {
643        match self {
644            Self::Con { span, .. }
645            | Self::App(_, _, span)
646            | Self::List(_, span)
647            | Self::Tuple(_, span)
648            | Self::Fun(_, _, span)
649            | Self::Var(_, span)
650            | Self::Unit(span)
651            | Self::Constrained(_, span)
652            | Self::Lit { span, .. } => *span,
653        }
654    }
655
656    pub(crate) const fn with_span(mut self, span: Span) -> Self {
657        match &mut self {
658            Self::Con { span: s, .. }
659            | Self::App(_, _, s)
660            | Self::List(_, s)
661            | Self::Tuple(_, s)
662            | Self::Fun(_, _, s)
663            | Self::Var(_, s)
664            | Self::Unit(s)
665            | Self::Constrained(_, s)
666            | Self::Lit { span: s, .. } => *s = span,
667        }
668        self
669    }
670}
671
672#[derive(Debug, Clone, PartialEq, Eq)]
673#[non_exhaustive]
674pub enum TypeAnnotation {
675    /// The source construct did not include a type annotation.
676    Absent,
677    /// The source construct included a type annotation that parsed cleanly.
678    Present(Type),
679    /// The source construct included a type annotation, but the parser could
680    /// not model it as a [`Type`]. Diagnostics carry the detailed message.
681    Malformed { span: Span },
682}
683
684impl TypeAnnotation {
685    #[must_use]
686    pub const fn as_type(&self) -> Option<&Type> {
687        match self {
688            Self::Present(ty) => Some(ty),
689            Self::Absent | Self::Malformed { .. } => None,
690        }
691    }
692
693    #[must_use]
694    pub const fn is_absent(&self) -> bool {
695        matches!(self, Self::Absent)
696    }
697
698    #[must_use]
699    pub const fn is_malformed(&self) -> bool {
700        matches!(self, Self::Malformed { .. })
701    }
702}
703
704#[derive(Debug, Clone, PartialEq, Eq)]
705pub struct FieldDecl {
706    /// Field or method name.
707    pub name: Identifier,
708    /// Structured field type parse state.
709    pub ty: TypeAnnotation,
710    /// Position of the field/method name.
711    pub pos: Pos,
712    /// Span of the full field declaration (`name : Type` when present).
713    pub span: Span,
714}
715
716#[derive(Debug, Clone, Copy, PartialEq, Eq)]
717#[non_exhaustive]
718pub enum Consuming {
719    /// Daml `consuming` choice.
720    Consuming,
721    /// Daml `nonconsuming` choice.
722    NonConsuming,
723    /// Legacy/pre-Daml-3 `preconsuming` spelling.
724    PreConsuming,
725    /// Legacy/pre-Daml-3 `postconsuming` spelling.
726    PostConsuming,
727}
728
729#[derive(Debug, Clone, PartialEq, Eq)]
730pub struct ChoiceDecl {
731    /// Choice name.
732    pub name: Identifier,
733    /// Consuming mode parsed from the choice header.
734    pub consuming: Consuming,
735    /// Structured return type parse state.
736    pub return_ty: TypeAnnotation,
737    /// Choice parameter fields in source order.
738    pub params: Vec<FieldDecl>,
739    /// Comma-separated controller expressions.
740    pub controllers: Vec<Expr>,
741    /// Choice observers, if any.
742    pub observers: Vec<Expr>,
743    /// Choice body after `do`; `None` when the parser did not find one.
744    pub body: Option<Expr>,
745    /// Position of the `choice` token.
746    pub pos: Pos,
747    /// Span of the whole choice declaration.
748    pub span: Span,
749}
750
751#[derive(Debug, Clone, PartialEq, Eq)]
752#[non_exhaustive]
753pub enum TemplateBodyDecl {
754    /// `signatory` clause with source-ordered party expressions.
755    Signatory {
756        /// Party expressions in source order.
757        parties: Vec<Expr>,
758        /// Position of the `signatory` token.
759        pos: Pos,
760        /// Span of the whole clause.
761        span: Span,
762    },
763    /// `observer` clause with source-ordered party expressions.
764    Observer {
765        /// Party expressions in source order.
766        parties: Vec<Expr>,
767        /// Position of the `observer` token.
768        pos: Pos,
769        /// Span of the whole clause.
770        span: Span,
771    },
772    /// `ensure` clause.
773    Ensure {
774        /// Predicate expression after `ensure`.
775        expr: Expr,
776        /// Position of the `ensure` token.
777        pos: Pos,
778        /// Span of the whole clause.
779        span: Span,
780    },
781    /// Template `key` declaration.
782    Key {
783        /// Key expression.
784        expr: Expr,
785        /// Structured key type parse state.
786        ty: TypeAnnotation,
787        /// Position of the `key` token.
788        pos: Pos,
789        /// Span of the whole key declaration.
790        span: Span,
791    },
792    /// `maintainer` clause.
793    Maintainer {
794        /// Maintainer expression.
795        expr: Expr,
796        /// Position of the `maintainer` token.
797        pos: Pos,
798        /// Span of the whole clause.
799        span: Span,
800    },
801    /// `choice` declaration.
802    Choice(ChoiceDecl),
803    /// `interface instance` declaration nested in a template.
804    InterfaceInstance(InterfaceInstanceDecl),
805    /// `agreement`, `let` blocks, deprecated `controller ... can`, etc.
806    Other {
807        /// Raw source text preserved for unsupported/malformed body syntax.
808        raw: String,
809        /// Position of the raw body's first token.
810        pos: Pos,
811        /// Span of the preserved raw body text.
812        span: Span,
813    },
814}
815
816#[derive(Debug, Clone, PartialEq, Eq)]
817pub struct InterfaceInstanceDecl {
818    /// Interface being implemented (`Disclosure.I`).
819    pub interface_name: ModuleName,
820    /// Explicit template from `for Foo`; `None` when omitted (the enclosing
821    /// template when declared inside one).
822    pub for_template: Option<ModuleName>,
823    /// Method implementations: name → bound expression.
824    pub methods: Vec<Binding>,
825    /// Position of the `interface instance` clause.
826    pub pos: Pos,
827    /// Span of the whole interface instance declaration.
828    pub span: Span,
829}
830
831#[derive(Debug, Clone, PartialEq, Eq)]
832pub struct TemplateDecl {
833    /// Template name.
834    pub name: Identifier,
835    /// Template fields in source order.
836    pub fields: Vec<FieldDecl>,
837    /// Template body declarations in source order.
838    pub body: Vec<TemplateBodyDecl>,
839    /// Position of the `template` token.
840    pub pos: Pos,
841    /// Span of the whole template declaration.
842    pub span: Span,
843}
844
845#[derive(Debug, Clone, PartialEq, Eq)]
846pub struct InterfaceDecl {
847    /// Interface name.
848    pub name: Identifier,
849    /// Interfaces this interface requires (`requires Lockable.I, ...`).
850    pub requires: Vec<ModuleName>,
851    /// Optional view type name from `viewtype`.
852    pub viewtype: Option<ModuleName>,
853    /// Method signatures in source order.
854    pub methods: Vec<FieldDecl>,
855    /// Interface choices in source order.
856    pub choices: Vec<ChoiceDecl>,
857    /// Position of the `interface` token.
858    pub pos: Pos,
859    /// Span of the whole interface declaration.
860    pub span: Span,
861}
862
863#[derive(Debug, Clone, PartialEq, Eq)]
864pub struct Equation {
865    /// Parameter patterns in source order.
866    pub params: Vec<Pat>,
867    /// Unguarded body or first guarded body for convenience.
868    pub body: Expr,
869    /// Guarded equations keep their guards as (guard, body) pairs; `body`
870    /// then holds the first guarded body for convenience.
871    pub guards: Vec<(Expr, Expr)>,
872    /// `where` helper bindings attached to this equation.
873    pub where_bindings: Vec<Binding>,
874    /// Position of the equation's first token.
875    pub pos: Pos,
876    /// Span of this equation only.
877    pub span: Span,
878}
879
880#[derive(Debug, Clone, PartialEq, Eq)]
881pub struct FunctionDecl {
882    /// Function name.
883    pub name: Identifier,
884    /// Standalone signature type parse state.
885    pub ty: TypeAnnotation,
886    /// Equations for this function in source order.
887    pub equations: Vec<Equation>,
888    /// Position of the function's first appearance.
889    pub pos: Pos,
890    /// Span of the function's first appearance (signature or first equation).
891    /// Convenience anchor; a multi-equation function's precise ranges are the
892    /// per-`Equation` spans, since equations need not be contiguous in source.
893    pub span: Span,
894    /// Span of the standalone type signature `name : Type`, if one was seen.
895    pub sig_span: Option<Span>,
896}
897
898#[derive(Debug, Clone, PartialEq, Eq)]
899pub struct ImportDecl {
900    /// Imported module path.
901    pub module_name: ModuleName,
902    /// Whether the import is qualified.
903    pub style: ImportStyle,
904    /// Optional module alias from `as`.
905    pub alias: Option<ModuleName>,
906    /// Position of the `import` token.
907    pub pos: Pos,
908    /// Span of the whole import declaration.
909    pub span: Span,
910}
911
912#[derive(Debug, Clone, PartialEq, Eq)]
913#[non_exhaustive]
914pub enum Decl {
915    /// Template declaration.
916    Template(TemplateDecl),
917    /// Interface declaration.
918    Interface(InterfaceDecl),
919    /// Function signature/equations grouped by name.
920    Function(FunctionDecl),
921    /// data/type/class/instance/exception — recorded with name + span.
922    TypeDef {
923        /// Declaration keyword (`data`, `type`, `class`, ...).
924        keyword: String,
925        /// Declared type/class/instance name as parsed.
926        name: Identifier,
927        /// Position of the declaration keyword.
928        pos: Pos,
929        /// Span of the declaration header/body consumed by the parser.
930        span: Span,
931    },
932    /// Anything unparseable at the top level (diagnostic already emitted).
933    Unknown {
934        /// Raw source text of the skipped declaration.
935        raw: String,
936        /// Position of the skipped declaration's first token.
937        pos: Pos,
938        /// Span of the skipped declaration text.
939        span: Span,
940    },
941}
942
943#[derive(Debug, Clone, PartialEq, Eq)]
944pub struct Module {
945    /// Module name from the header, or `Unknown` when the source has no header.
946    pub name: ModuleName,
947    /// Position of the `module` keyword, or the start of the fallback module.
948    pub pos: Pos,
949    /// Whole-module extent: `[0, source.len())`. Container for all decls.
950    pub span: Span,
951    /// Span of the `module M (...) where` header clause; empty when the file
952    /// has no module header. Lets the span oracle treat header tokens as
953    /// covered without a dedicated header node.
954    pub header: Span,
955    /// Imports in source order.
956    pub imports: Vec<ImportDecl>,
957    /// Top-level declarations in source order.
958    pub decls: Vec<Decl>,
959}
960
961/// Why a [`ParseDiagnostic`] fired.
962///
963/// Lets a consumer separate syntax the parser deliberately does not model (still
964/// safe, just unanalyzed) from a genuine malformation, a recursion-limit
965/// degradation, or a lexical error.
966#[derive(Debug, Clone, Copy, PartialEq, Eq)]
967#[non_exhaustive]
968pub enum DiagnosticCategory {
969    /// A whole declaration could not be parsed and was skipped to the next item.
970    SkippedDecl,
971    /// A malformed expression, pattern, or expected-token error inside an
972    /// otherwise-recognized construct.
973    Malformed,
974    /// A construct the parser intentionally does not support, e.g. legacy
975    /// `controller ... can` choice syntax.
976    UnsupportedSyntax,
977    /// Expression/pattern nesting exceeded the recursion bound and was degraded
978    /// to raw text.
979    RecursionLimit,
980    /// A lexical error (unterminated string/comment, stray character).
981    Lex,
982}
983
984impl DiagnosticCategory {
985    /// Stable kebab-case tag for machine-readable output (JSON/SARIF) and logs.
986    #[must_use]
987    pub const fn as_str(self) -> &'static str {
988        match self {
989            Self::SkippedDecl => "skipped-declaration",
990            Self::Malformed => "malformed",
991            Self::UnsupportedSyntax => "unsupported-syntax",
992            Self::RecursionLimit => "recursion-limit",
993            Self::Lex => "lexical-error",
994        }
995    }
996}
997
998impl std::fmt::Display for DiagnosticCategory {
999    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1000        f.write_str(self.as_str())
1001    }
1002}
1003
1004/// Machine-readable reason a [`ParseDiagnostic`] fired.
1005///
1006/// Keep [`ParseDiagnostic::message`] for presentation. Match on this enum when
1007/// downstream code needs stable behavior for recoverable parser failures.
1008#[derive(Debug, Clone, PartialEq, Eq)]
1009#[non_exhaustive]
1010pub enum ParseDiagnosticKind {
1011    /// The lexer reported malformed source; the original lexical kind is
1012    /// preserved so callers do not need to parse the human message.
1013    Lex(crate::lexer::LexErrorKind),
1014    /// The parser expected a specific token or token class and recovered.
1015    ExpectedToken(ExpectedToken),
1016    /// A type annotation was present but could not be parsed as a `Type`.
1017    MalformedTypeAnnotation(TypeAnnotationContext),
1018    /// A recognized construct was malformed in a way that is not only an
1019    /// expected-token miss.
1020    MalformedSyntax(MalformedSyntaxKind),
1021    /// A whole declaration could not be parsed and was skipped.
1022    SkippedDeclaration(SkippedDeclarationReason),
1023    /// The source used syntax this parser intentionally does not model.
1024    UnsupportedSyntax(UnsupportedSyntaxKind),
1025    /// Expression or pattern nesting exceeded the parser recursion bound.
1026    RecursionLimit { limit: u32 },
1027}
1028
1029impl ParseDiagnosticKind {
1030    /// Coarse diagnostic class retained for stable JSON/SARIF tags.
1031    #[must_use]
1032    pub const fn category(&self) -> DiagnosticCategory {
1033        match self {
1034            Self::Lex(_) => DiagnosticCategory::Lex,
1035            Self::ExpectedToken(_)
1036            | Self::MalformedTypeAnnotation(_)
1037            | Self::MalformedSyntax(_) => DiagnosticCategory::Malformed,
1038            Self::SkippedDeclaration(_) => DiagnosticCategory::SkippedDecl,
1039            Self::UnsupportedSyntax(_) => DiagnosticCategory::UnsupportedSyntax,
1040            Self::RecursionLimit { .. } => DiagnosticCategory::RecursionLimit,
1041        }
1042    }
1043}
1044
1045/// Expected token or token class for a recoverable parser diagnostic.
1046#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1047#[non_exhaustive]
1048pub enum ExpectedToken {
1049    WhereAfterModuleHeader,
1050    ModuleNameAfterImport,
1051    TemplateNameAfterInterfaceInstanceFor,
1052    FieldNameTypePair,
1053    EqualsAfterGuard,
1054    EqualsOrGuardedRightHandSide,
1055    ProjectionFieldAfterDot,
1056    ThenKeyword,
1057    ElseKeyword,
1058    OfKeywordInCaseExpression,
1059    ArrowInGuardedCaseAlternative,
1060    ArrowInCaseAlternative,
1061}
1062
1063/// The declaration context whose type annotation was malformed.
1064#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1065#[non_exhaustive]
1066pub enum TypeAnnotationContext {
1067    Field,
1068    Key,
1069    Choice,
1070    InterfaceMethod,
1071    Function,
1072}
1073
1074impl TypeAnnotationContext {
1075    #[must_use]
1076    pub const fn as_str(self) -> &'static str {
1077        match self {
1078            Self::Field => "field",
1079            Self::Key => "key",
1080            Self::Choice => "choice",
1081            Self::InterfaceMethod => "interface method",
1082            Self::Function => "function",
1083        }
1084    }
1085}
1086
1087/// Recoverable malformed syntax cases that are not just missing one token.
1088#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1089#[non_exhaustive]
1090pub enum MalformedSyntaxKind {
1091    FunctionEquation,
1092    FunctionParameterPattern,
1093    LambdaParameter,
1094}
1095
1096/// Why a whole declaration was skipped.
1097#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1098#[non_exhaustive]
1099pub enum SkippedDeclarationReason {
1100    TopLevelPatternBinding,
1101    UnrecognizedDeclaration,
1102}
1103
1104/// Unsupported syntax families surfaced by the parser.
1105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1106#[non_exhaustive]
1107pub enum UnsupportedSyntaxKind {
1108    LegacyControllerCan,
1109}
1110
1111/// Parse diagnostic — never fatal under tolerant parsing.
1112///
1113/// Under [`crate::parse::parse_module`] the scan continues. Strict callers that
1114/// use [`crate::parse::parse_module_strict`] or
1115/// [`crate::parse::ParseModuleResult::into_result`] treat any diagnostic as
1116/// [`crate::parse::ParseModuleError`].
1117#[derive(Debug, Clone, PartialEq, Eq)]
1118pub struct ParseDiagnostic {
1119    /// Machine-readable reason for the diagnostic.
1120    pub kind: ParseDiagnosticKind,
1121    /// Human-readable presentation message. Use [`Self::kind`] for logic.
1122    pub message: String,
1123    pub pos: Pos,
1124    /// Byte span of the offending region. The end is the actionable addition
1125    /// over `pos`-alone; zero-width when only a point is known (lex errors,
1126    /// EOF).
1127    pub span: Span,
1128    pub category: DiagnosticCategory,
1129}
1130
1131impl ParseDiagnostic {
1132    #[must_use]
1133    pub fn new(
1134        kind: ParseDiagnosticKind,
1135        message: impl Into<String>,
1136        pos: Pos,
1137        span: Span,
1138    ) -> Self {
1139        let category = kind.category();
1140        Self {
1141            kind,
1142            message: message.into(),
1143            pos,
1144            span,
1145            category,
1146        }
1147    }
1148
1149    /// Human-readable presentation message.
1150    #[must_use]
1151    pub fn message(&self) -> &str {
1152        &self.message
1153    }
1154
1155    /// Machine-readable diagnostic reason.
1156    #[must_use]
1157    pub const fn kind(&self) -> &ParseDiagnosticKind {
1158        &self.kind
1159    }
1160
1161    /// Coarse recovery category.
1162    #[must_use]
1163    pub const fn category(&self) -> DiagnosticCategory {
1164        self.category
1165    }
1166}
1167
1168impl std::fmt::Display for ParseDiagnostic {
1169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1170        self.message.fmt(f)
1171    }
1172}
1173
1174impl std::error::Error for ParseDiagnostic {}
1175
1176impl Expr {
1177    #[must_use]
1178    pub const fn pos(&self) -> Pos {
1179        match self {
1180            Self::Var { pos, .. }
1181            | Self::Con { pos, .. }
1182            | Self::Lit { pos, .. }
1183            | Self::App { pos, .. }
1184            | Self::BinOp { pos, .. }
1185            | Self::Neg { pos, .. }
1186            | Self::Lambda { pos, .. }
1187            | Self::If { pos, .. }
1188            | Self::Case { pos, .. }
1189            | Self::Do { pos, .. }
1190            | Self::LetIn { pos, .. }
1191            | Self::Record { pos, .. }
1192            | Self::Tuple { pos, .. }
1193            | Self::List { pos, .. }
1194            | Self::Try { pos, .. }
1195            | Self::OperatorRef { pos, .. }
1196            | Self::LeftSection { pos, .. }
1197            | Self::RightSection { pos, .. }
1198            | Self::Error { pos, .. } => *pos,
1199        }
1200    }
1201
1202    /// Byte span covering the whole expression.
1203    #[must_use]
1204    pub const fn span(&self) -> Span {
1205        match self {
1206            Self::Var { span, .. }
1207            | Self::Con { span, .. }
1208            | Self::Lit { span, .. }
1209            | Self::App { span, .. }
1210            | Self::BinOp { span, .. }
1211            | Self::Neg { span, .. }
1212            | Self::Lambda { span, .. }
1213            | Self::If { span, .. }
1214            | Self::Case { span, .. }
1215            | Self::Do { span, .. }
1216            | Self::LetIn { span, .. }
1217            | Self::Record { span, .. }
1218            | Self::Tuple { span, .. }
1219            | Self::List { span, .. }
1220            | Self::Try { span, .. }
1221            | Self::OperatorRef { span, .. }
1222            | Self::LeftSection { span, .. }
1223            | Self::RightSection { span, .. }
1224            | Self::Error { span, .. } => *span,
1225        }
1226    }
1227
1228    /// Render back to compact, source-*like* text for diagnostics and `raw`
1229    /// fields.
1230    ///
1231    /// This is **lossy and normalizing**, not byte-faithful: original layout is
1232    /// dropped (e.g. `do`/`let` statements are joined with `; `), operators and
1233    /// spacing are normalized, and comments/trivia are gone. Use it for a quick
1234    /// human-readable echo of an expression; for source-exact reconstruction use
1235    /// the node's [`span`](Self::span) into the original text (that is how
1236    /// `daml-fmt` and [`crate::ast_span::render_from_ast`] stay lossless).
1237    #[must_use]
1238    pub fn render(&self) -> String {
1239        match self {
1240            Self::Var {
1241                qualifier, name, ..
1242            }
1243            | Self::Con {
1244                qualifier, name, ..
1245            } => qualifier
1246                .as_ref()
1247                .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
1248            Self::Lit { kind, text, .. } => match kind {
1249                LitKind::Text => format!("{text:?}"),
1250                LitKind::Char => format!("'{text}'"),
1251                _ => text.clone(),
1252            },
1253            Self::App { func, args, .. } => {
1254                let mut s = func.render_atomic();
1255                for a in args {
1256                    s.push(' ');
1257                    s.push_str(&a.render_atomic());
1258                }
1259                s
1260            }
1261            Self::BinOp { op, lhs, rhs, .. } => {
1262                if *op == "." {
1263                    // Record projection / composition: `account.custodian`.
1264                    format!("{}.{}", lhs.render_atomic(), rhs.render_atomic())
1265                } else {
1266                    format!("{} {} {}", lhs.render_atomic(), op, rhs.render_atomic())
1267                }
1268            }
1269            Self::Neg { expr, .. } => format!("-{}", expr.render_atomic()),
1270            Self::Lambda { params, body, .. } => {
1271                let ps: Vec<String> = params.iter().map(|p| p.render()).collect();
1272                format!("\\{} -> {}", ps.join(" "), body.render())
1273            }
1274            Self::If {
1275                cond,
1276                then_branch,
1277                else_branch,
1278                ..
1279            } => format!(
1280                "if {} then {} else {}",
1281                cond.render(),
1282                then_branch.render(),
1283                else_branch.render()
1284            ),
1285            Self::Case {
1286                scrutinee, alts, ..
1287            } => {
1288                let arms: Vec<String> = alts
1289                    .iter()
1290                    .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
1291                    .collect();
1292                format!("case {} of {}", scrutinee.render(), arms.join("; "))
1293            }
1294            Self::Do { stmts, .. } => {
1295                let body: Vec<String> = stmts.iter().map(render_do_stmt).collect();
1296                format!("do {}", body.join("; "))
1297            }
1298            Self::LetIn { bindings, body, .. } => {
1299                let bs: Vec<String> = bindings.iter().map(render_binding).collect();
1300                format!("let {} in {}", bs.join("; "), body.render())
1301            }
1302            Self::Record { base, fields, .. } => {
1303                let fs: Vec<String> = fields
1304                    .iter()
1305                    .map(|f| match f {
1306                        FieldAssign::Assign { name, value, .. } => {
1307                            format!("{} = {}", name, value.render())
1308                        }
1309                        FieldAssign::Pun { name, .. } => name.to_string(),
1310                        FieldAssign::Wildcard { .. } => "..".to_string(),
1311                    })
1312                    .collect();
1313                format!("{} with {}", base.render_atomic(), fs.join("; "))
1314            }
1315            Self::Tuple { items, .. } => {
1316                let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
1317                format!("({})", xs.join(", "))
1318            }
1319            Self::List { items, .. } => {
1320                let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
1321                format!("[{}]", xs.join(", "))
1322            }
1323            Self::Try { body, handlers, .. } => {
1324                let hs: Vec<String> = handlers
1325                    .iter()
1326                    .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
1327                    .collect();
1328                format!("try {} catch {}", body.render(), hs.join("; "))
1329            }
1330            Self::OperatorRef { op, .. } => format!("({op})"),
1331            Self::LeftSection { op, operand, .. } => format!("({} {})", operand.render(), op),
1332            Self::RightSection { op, operand, .. } => format!("({} {})", op, operand.render()),
1333            Self::Error { raw, .. } => raw.clone(),
1334        }
1335    }
1336
1337    /// Render with parentheses if this expression wouldn't survive as an
1338    /// application argument.
1339    fn render_atomic(&self) -> String {
1340        match self {
1341            Self::Var { .. }
1342            | Self::Con { .. }
1343            | Self::Lit { .. }
1344            | Self::Tuple { .. }
1345            | Self::List { .. }
1346            | Self::OperatorRef { .. }
1347            | Self::LeftSection { .. }
1348            | Self::RightSection { .. }
1349            | Self::Error { .. } => self.render(),
1350            _ => format!("({})", self.render()),
1351        }
1352    }
1353
1354    /// The head of an application spine: for `Foo.exercise cid X`, the
1355    /// `Foo.exercise` Var. For non-apps, the expression itself.
1356    #[must_use]
1357    pub fn application_head(&self) -> &Self {
1358        match self {
1359            Self::App { func, .. } => func.application_head(),
1360            _ => self,
1361        }
1362    }
1363
1364    /// Application arguments, empty for non-apps. The `App` spine is flattened
1365    /// (see the [`App`](Self::App) variant), so for `f a b c` this returns all
1366    /// three arguments `[a, b, c]`, not a single curried layer.
1367    #[must_use]
1368    pub fn application_args(&self) -> &[Self] {
1369        match self {
1370            Self::App { args, .. } => args,
1371            _ => &[],
1372        }
1373    }
1374}
1375
1376fn render_do_stmt(s: &DoStmt) -> String {
1377    match s {
1378        DoStmt::Bind { pat, expr, .. } => format!("{} <- {}", pat.render(), expr.render()),
1379        DoStmt::Let { bindings, .. } => {
1380            let bs: Vec<String> = bindings.iter().map(render_binding).collect();
1381            format!("let {}", bs.join("; "))
1382        }
1383        DoStmt::Expr { expr, .. } => expr.render(),
1384    }
1385}
1386
1387fn render_binding(b: &Binding) -> String {
1388    let mut s = b.pat.render();
1389    for p in &b.params {
1390        s.push(' ');
1391        s.push_str(&p.render());
1392    }
1393    format!("{} = {}", s, b.expr.render())
1394}
1395
1396impl Pat {
1397    #[must_use]
1398    pub const fn pos(&self) -> Pos {
1399        match self {
1400            Self::Var { pos, .. }
1401            | Self::Wild { pos, .. }
1402            | Self::Con { pos, .. }
1403            | Self::Tuple { pos, .. }
1404            | Self::List { pos, .. }
1405            | Self::Lit { pos, .. }
1406            | Self::As { pos, .. }
1407            | Self::Other { pos, .. } => *pos,
1408        }
1409    }
1410
1411    /// Byte span covering the whole pattern.
1412    #[must_use]
1413    pub const fn span(&self) -> Span {
1414        match self {
1415            Self::Var { span, .. }
1416            | Self::Wild { span, .. }
1417            | Self::Con { span, .. }
1418            | Self::Tuple { span, .. }
1419            | Self::List { span, .. }
1420            | Self::Lit { span, .. }
1421            | Self::As { span, .. }
1422            | Self::Other { span, .. } => *span,
1423        }
1424    }
1425
1426    /// Render back to compact, source-*like* text. Lossy and normalizing in the
1427    /// same way as [`Expr::render`]; use the node's [`span`](Self::span) for
1428    /// byte-faithful text.
1429    #[must_use]
1430    pub fn render(&self) -> String {
1431        match self {
1432            Self::Var { name, .. } => name.to_string(),
1433            Self::Wild { .. } => "_".to_string(),
1434            Self::Con {
1435                qualifier,
1436                name,
1437                args,
1438                ..
1439            } => {
1440                let head = qualifier
1441                    .as_ref()
1442                    .map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
1443                if args.is_empty() {
1444                    head
1445                } else {
1446                    let parts: Vec<String> = args.iter().map(|p| p.render()).collect();
1447                    format!("({} {})", head, parts.join(" "))
1448                }
1449            }
1450            Self::Tuple { items, .. } => {
1451                let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
1452                format!("({})", xs.join(", "))
1453            }
1454            Self::List { items, .. } => {
1455                let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
1456                format!("[{}]", xs.join(", "))
1457            }
1458            Self::Lit { kind, text, .. } => match kind {
1459                LitKind::Text => format!("{text:?}"),
1460                LitKind::Char => format!("'{text}'"),
1461                _ => text.clone(),
1462            },
1463            Self::As { name, pat, .. } => format!("{}@{}", name, pat.render()),
1464            Self::Other { raw, .. } => raw.clone(),
1465        }
1466    }
1467}
1468
1469impl std::fmt::Display for Expr {
1470    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1471        f.write_str(&self.render())
1472    }
1473}
1474
1475impl std::fmt::Display for Pat {
1476    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1477        f.write_str(&self.render())
1478    }
1479}
1480
1481// Span invariants for the lossless AST tile layer; render-shape tests live in integration tests.
1482#[cfg(test)]
1483mod tests {
1484    use super::*;
1485
1486    fn span(start: usize, end: usize) -> Span {
1487        Span::from_usize(start, end)
1488    }
1489
1490    #[test]
1491    fn span_distinguishes_empty_from_invalid() {
1492        assert!(span(3, 3).is_valid());
1493        assert!(span(3, 3).is_empty());
1494
1495        assert!(!span(4, 3).is_valid());
1496        assert!(!span(4, 3).is_empty());
1497    }
1498
1499    #[test]
1500    fn contains_rejects_invalid_spans() {
1501        let parent = span(1, 10);
1502
1503        assert!(parent.contains(&span(3, 7)));
1504        assert!(!parent.contains(&span(7, 3)));
1505        assert!(!span(10, 1).contains(&span(3, 7)));
1506    }
1507
1508    #[test]
1509    fn span_range_and_get_share_source_bytes_safely() {
1510        let source = "foo: Int";
1511
1512        assert_eq!(span(0, 3).range(), 0..3);
1513        assert_eq!(span(0, 3).get(source), Some("foo"));
1514        assert_eq!(span(3, 7).get(source), Some(": In"));
1515        assert!(span(3, 100).get(source).is_none());
1516    }
1517}