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/// Fixity associativity keyword (`infix`, `infixl`, `infixr`).
899#[derive(Debug, Clone, Copy, PartialEq, Eq)]
900#[non_exhaustive]
901pub enum FixityAssoc {
902    Infix,
903    InfixL,
904    InfixR,
905}
906
907/// Operator or backtick-quoted name in a fixity declaration.
908#[derive(Debug, Clone, PartialEq, Eq)]
909#[non_exhaustive]
910pub enum FixityTarget {
911    /// Symbolic operator such as `===` or `>=>`.
912    Operator(Operator),
913    /// Backtick-quoted identifier such as `` `Pair` ``.
914    Backtick(Identifier),
915}
916
917/// Top-level fixity declaration (`infix[l|r]? n op [, op ...]`).
918#[derive(Debug, Clone, PartialEq, Eq)]
919pub struct FixityDecl {
920    pub assoc: FixityAssoc,
921    pub precedence: u8,
922    pub operators: Vec<FixityTarget>,
923    pub pos: Pos,
924    pub span: Span,
925}
926
927#[derive(Debug, Clone, PartialEq, Eq)]
928pub struct ImportDecl {
929    /// Imported module path.
930    pub module_name: ModuleName,
931    /// Whether the import is qualified.
932    pub style: ImportStyle,
933    /// Optional module alias from `as`.
934    pub alias: Option<ModuleName>,
935    /// Position of the `import` token.
936    pub pos: Pos,
937    /// Span of the whole import declaration.
938    pub span: Span,
939}
940
941#[derive(Debug, Clone, PartialEq, Eq)]
942#[non_exhaustive]
943pub enum Decl {
944    /// Template declaration.
945    Template(TemplateDecl),
946    /// Interface declaration.
947    Interface(InterfaceDecl),
948    /// Function signature/equations grouped by name.
949    Function(FunctionDecl),
950    /// data/type/class/instance/exception — recorded with name + span.
951    TypeDef {
952        /// Declaration keyword (`data`, `type`, `class`, ...).
953        keyword: String,
954        /// Declared type/class/instance name as parsed.
955        name: Identifier,
956        /// Position of the declaration keyword.
957        pos: Pos,
958        /// Span of the declaration header/body consumed by the parser.
959        span: Span,
960    },
961    /// Top-level fixity declaration.
962    Fixity(FixityDecl),
963    /// Top-level syntax the parser recognizes but intentionally does not model.
964    UnsupportedSyntax {
965        /// Why the declaration is unsupported.
966        kind: UnsupportedSyntaxKind,
967        /// Raw source text of the declaration.
968        raw: String,
969        /// Position of the declaration's first token.
970        pos: Pos,
971        /// Span of the declaration text.
972        span: Span,
973    },
974    /// Anything unparseable at the top level (diagnostic already emitted).
975    Unknown {
976        /// Raw source text of the skipped declaration.
977        raw: String,
978        /// Position of the skipped declaration's first token.
979        pos: Pos,
980        /// Span of the skipped declaration text.
981        span: Span,
982    },
983}
984
985#[derive(Debug, Clone, PartialEq, Eq)]
986pub struct Module {
987    /// Module name from the header, or `Unknown` when the source has no header.
988    pub name: ModuleName,
989    /// Position of the `module` keyword, or the start of the fallback module.
990    pub pos: Pos,
991    /// Whole-module extent: `[0, source.len())`. Container for all decls.
992    pub span: Span,
993    /// Span of the `module M (...) where` header clause; empty when the file
994    /// has no module header. Lets the span oracle treat header tokens as
995    /// covered without a dedicated header node.
996    pub header: Span,
997    /// Imports in source order.
998    pub imports: Vec<ImportDecl>,
999    /// Top-level declarations in source order.
1000    pub decls: Vec<Decl>,
1001}
1002
1003/// Why a [`ParseDiagnostic`] fired.
1004///
1005/// Lets a consumer separate syntax the parser deliberately does not model (still
1006/// safe, just unanalyzed) from a genuine malformation, a recursion-limit
1007/// degradation, or a lexical error.
1008#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1009#[non_exhaustive]
1010pub enum DiagnosticCategory {
1011    /// A whole declaration could not be parsed and was skipped to the next item.
1012    SkippedDecl,
1013    /// A malformed expression, pattern, or expected-token error inside an
1014    /// otherwise-recognized construct.
1015    Malformed,
1016    /// A construct the parser intentionally does not support, e.g. legacy
1017    /// `controller ... can` choice syntax.
1018    UnsupportedSyntax,
1019    /// Expression/pattern nesting exceeded the recursion bound and was degraded
1020    /// to raw text.
1021    RecursionLimit,
1022    /// A lexical error (unterminated string/comment, stray character).
1023    Lex,
1024}
1025
1026impl DiagnosticCategory {
1027    /// Stable kebab-case tag for machine-readable output (JSON/SARIF) and logs.
1028    #[must_use]
1029    pub const fn as_str(self) -> &'static str {
1030        match self {
1031            Self::SkippedDecl => "skipped-declaration",
1032            Self::Malformed => "malformed",
1033            Self::UnsupportedSyntax => "unsupported-syntax",
1034            Self::RecursionLimit => "recursion-limit",
1035            Self::Lex => "lexical-error",
1036        }
1037    }
1038}
1039
1040impl std::fmt::Display for DiagnosticCategory {
1041    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1042        f.write_str(self.as_str())
1043    }
1044}
1045
1046/// Machine-readable reason a [`ParseDiagnostic`] fired.
1047///
1048/// Keep [`ParseDiagnostic::message`] for presentation. Match on this enum when
1049/// downstream code needs stable behavior for recoverable parser failures.
1050#[derive(Debug, Clone, PartialEq, Eq)]
1051#[non_exhaustive]
1052pub enum ParseDiagnosticKind {
1053    /// The lexer reported malformed source; the original lexical kind is
1054    /// preserved so callers do not need to parse the human message.
1055    Lex(crate::lexer::LexErrorKind),
1056    /// The parser expected a specific token or token class and recovered.
1057    ExpectedToken(ExpectedToken),
1058    /// A type annotation was present but could not be parsed as a `Type`.
1059    MalformedTypeAnnotation(TypeAnnotationContext),
1060    /// A recognized construct was malformed in a way that is not only an
1061    /// expected-token miss.
1062    MalformedSyntax(MalformedSyntaxKind),
1063    /// A whole declaration could not be parsed and was skipped.
1064    SkippedDeclaration(SkippedDeclarationReason),
1065    /// The source used syntax this parser intentionally does not model.
1066    UnsupportedSyntax(UnsupportedSyntaxKind),
1067    /// Expression or pattern nesting exceeded the parser recursion bound.
1068    RecursionLimit { limit: u32 },
1069}
1070
1071impl ParseDiagnosticKind {
1072    /// Coarse diagnostic class retained for stable JSON/SARIF tags.
1073    #[must_use]
1074    pub const fn category(&self) -> DiagnosticCategory {
1075        match self {
1076            Self::Lex(_) => DiagnosticCategory::Lex,
1077            Self::ExpectedToken(_)
1078            | Self::MalformedTypeAnnotation(_)
1079            | Self::MalformedSyntax(_) => DiagnosticCategory::Malformed,
1080            Self::SkippedDeclaration(_) => DiagnosticCategory::SkippedDecl,
1081            Self::UnsupportedSyntax(_) => DiagnosticCategory::UnsupportedSyntax,
1082            Self::RecursionLimit { .. } => DiagnosticCategory::RecursionLimit,
1083        }
1084    }
1085}
1086
1087/// Expected token or token class for a recoverable parser diagnostic.
1088#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1089#[non_exhaustive]
1090pub enum ExpectedToken {
1091    WhereAfterModuleHeader,
1092    ModuleNameAfterImport,
1093    TemplateNameAfterInterfaceInstanceFor,
1094    FieldNameTypePair,
1095    EqualsAfterGuard,
1096    EqualsOrGuardedRightHandSide,
1097    ProjectionFieldAfterDot,
1098    ThenKeyword,
1099    ElseKeyword,
1100    OfKeywordInCaseExpression,
1101    ArrowInGuardedCaseAlternative,
1102    ArrowInCaseAlternative,
1103}
1104
1105/// The declaration context whose type annotation was malformed.
1106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1107#[non_exhaustive]
1108pub enum TypeAnnotationContext {
1109    Field,
1110    Key,
1111    Choice,
1112    InterfaceMethod,
1113    Function,
1114}
1115
1116impl TypeAnnotationContext {
1117    #[must_use]
1118    pub const fn as_str(self) -> &'static str {
1119        match self {
1120            Self::Field => "field",
1121            Self::Key => "key",
1122            Self::Choice => "choice",
1123            Self::InterfaceMethod => "interface method",
1124            Self::Function => "function",
1125        }
1126    }
1127}
1128
1129/// Recoverable malformed syntax cases that are not just missing one token.
1130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1131#[non_exhaustive]
1132pub enum MalformedSyntaxKind {
1133    FunctionEquation,
1134    FunctionParameterPattern,
1135    LambdaParameter,
1136}
1137
1138/// Why a whole declaration was skipped.
1139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1140#[non_exhaustive]
1141pub enum SkippedDeclarationReason {
1142    TopLevelPatternBinding,
1143    UnrecognizedDeclaration,
1144}
1145
1146/// Unsupported syntax families surfaced by the parser.
1147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1148#[non_exhaustive]
1149pub enum UnsupportedSyntaxKind {
1150    LegacyControllerCan,
1151    PatternSynonym,
1152}
1153
1154/// Parse diagnostic — never fatal under tolerant parsing.
1155///
1156/// Under [`crate::parse::parse_module`] the scan continues. Strict callers that
1157/// use [`crate::parse::parse_module_strict`] or
1158/// [`crate::parse::ParseModuleResult::into_result`] treat any diagnostic as
1159/// [`crate::parse::ParseModuleError`].
1160#[derive(Debug, Clone, PartialEq, Eq)]
1161pub struct ParseDiagnostic {
1162    /// Machine-readable reason for the diagnostic.
1163    pub kind: ParseDiagnosticKind,
1164    /// Human-readable presentation message. Use [`Self::kind`] for logic.
1165    pub message: String,
1166    pub pos: Pos,
1167    /// Byte span of the offending region. The end is the actionable addition
1168    /// over `pos`-alone; zero-width when only a point is known (lex errors,
1169    /// EOF).
1170    pub span: Span,
1171    pub category: DiagnosticCategory,
1172}
1173
1174impl ParseDiagnostic {
1175    #[must_use]
1176    pub fn new(
1177        kind: ParseDiagnosticKind,
1178        message: impl Into<String>,
1179        pos: Pos,
1180        span: Span,
1181    ) -> Self {
1182        let category = kind.category();
1183        Self {
1184            kind,
1185            message: message.into(),
1186            pos,
1187            span,
1188            category,
1189        }
1190    }
1191
1192    /// Human-readable presentation message.
1193    #[must_use]
1194    pub fn message(&self) -> &str {
1195        &self.message
1196    }
1197
1198    /// Machine-readable diagnostic reason.
1199    #[must_use]
1200    pub const fn kind(&self) -> &ParseDiagnosticKind {
1201        &self.kind
1202    }
1203
1204    /// Coarse recovery category.
1205    #[must_use]
1206    pub const fn category(&self) -> DiagnosticCategory {
1207        self.category
1208    }
1209}
1210
1211impl std::fmt::Display for ParseDiagnostic {
1212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1213        self.message.fmt(f)
1214    }
1215}
1216
1217impl std::error::Error for ParseDiagnostic {}
1218
1219impl Expr {
1220    #[must_use]
1221    pub const fn pos(&self) -> Pos {
1222        match self {
1223            Self::Var { pos, .. }
1224            | Self::Con { pos, .. }
1225            | Self::Lit { pos, .. }
1226            | Self::App { pos, .. }
1227            | Self::BinOp { pos, .. }
1228            | Self::Neg { pos, .. }
1229            | Self::Lambda { pos, .. }
1230            | Self::If { pos, .. }
1231            | Self::Case { pos, .. }
1232            | Self::Do { pos, .. }
1233            | Self::LetIn { pos, .. }
1234            | Self::Record { pos, .. }
1235            | Self::Tuple { pos, .. }
1236            | Self::List { pos, .. }
1237            | Self::Try { pos, .. }
1238            | Self::OperatorRef { pos, .. }
1239            | Self::LeftSection { pos, .. }
1240            | Self::RightSection { pos, .. }
1241            | Self::Error { pos, .. } => *pos,
1242        }
1243    }
1244
1245    /// Byte span covering the whole expression.
1246    #[must_use]
1247    pub const fn span(&self) -> Span {
1248        match self {
1249            Self::Var { span, .. }
1250            | Self::Con { span, .. }
1251            | Self::Lit { span, .. }
1252            | Self::App { span, .. }
1253            | Self::BinOp { span, .. }
1254            | Self::Neg { span, .. }
1255            | Self::Lambda { span, .. }
1256            | Self::If { span, .. }
1257            | Self::Case { span, .. }
1258            | Self::Do { span, .. }
1259            | Self::LetIn { span, .. }
1260            | Self::Record { span, .. }
1261            | Self::Tuple { span, .. }
1262            | Self::List { span, .. }
1263            | Self::Try { span, .. }
1264            | Self::OperatorRef { span, .. }
1265            | Self::LeftSection { span, .. }
1266            | Self::RightSection { span, .. }
1267            | Self::Error { span, .. } => *span,
1268        }
1269    }
1270
1271    /// Render back to compact, source-*like* text for diagnostics and `raw`
1272    /// fields.
1273    ///
1274    /// This is **lossy and normalizing**, not byte-faithful: original layout is
1275    /// dropped (e.g. `do`/`let` statements are joined with `; `), operators and
1276    /// spacing are normalized, and comments/trivia are gone. Use it for a quick
1277    /// human-readable echo of an expression; for source-exact reconstruction use
1278    /// the node's [`span`](Self::span) into the original text (that is how
1279    /// `daml-fmt` and [`crate::ast_span::render_from_ast`] stay lossless).
1280    #[must_use]
1281    pub fn render(&self) -> String {
1282        match self {
1283            Self::Var {
1284                qualifier, name, ..
1285            }
1286            | Self::Con {
1287                qualifier, name, ..
1288            } => qualifier
1289                .as_ref()
1290                .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
1291            Self::Lit { kind, text, .. } => match kind {
1292                LitKind::Text => format!("{text:?}"),
1293                LitKind::Char => format!("'{text}'"),
1294                _ => text.clone(),
1295            },
1296            Self::App { func, args, .. } => {
1297                let mut s = func.render_atomic();
1298                for a in args {
1299                    s.push(' ');
1300                    s.push_str(&a.render_atomic());
1301                }
1302                s
1303            }
1304            Self::BinOp { op, lhs, rhs, .. } => {
1305                if *op == "." {
1306                    // Record projection / composition: `account.custodian`.
1307                    format!("{}.{}", lhs.render_atomic(), rhs.render_atomic())
1308                } else {
1309                    format!("{} {} {}", lhs.render_atomic(), op, rhs.render_atomic())
1310                }
1311            }
1312            Self::Neg { expr, .. } => format!("-{}", expr.render_atomic()),
1313            Self::Lambda { params, body, .. } => {
1314                let ps: Vec<String> = params.iter().map(|p| p.render()).collect();
1315                format!("\\{} -> {}", ps.join(" "), body.render())
1316            }
1317            Self::If {
1318                cond,
1319                then_branch,
1320                else_branch,
1321                ..
1322            } => format!(
1323                "if {} then {} else {}",
1324                cond.render(),
1325                then_branch.render(),
1326                else_branch.render()
1327            ),
1328            Self::Case {
1329                scrutinee, alts, ..
1330            } => {
1331                let arms: Vec<String> = alts
1332                    .iter()
1333                    .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
1334                    .collect();
1335                format!("case {} of {}", scrutinee.render(), arms.join("; "))
1336            }
1337            Self::Do { stmts, .. } => {
1338                let body: Vec<String> = stmts.iter().map(render_do_stmt).collect();
1339                format!("do {}", body.join("; "))
1340            }
1341            Self::LetIn { bindings, body, .. } => {
1342                let bs: Vec<String> = bindings.iter().map(render_binding).collect();
1343                format!("let {} in {}", bs.join("; "), body.render())
1344            }
1345            Self::Record { base, fields, .. } => {
1346                let fs: Vec<String> = fields
1347                    .iter()
1348                    .map(|f| match f {
1349                        FieldAssign::Assign { name, value, .. } => {
1350                            format!("{} = {}", name, value.render())
1351                        }
1352                        FieldAssign::Pun { name, .. } => name.to_string(),
1353                        FieldAssign::Wildcard { .. } => "..".to_string(),
1354                    })
1355                    .collect();
1356                format!("{} with {}", base.render_atomic(), fs.join("; "))
1357            }
1358            Self::Tuple { items, .. } => {
1359                let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
1360                format!("({})", xs.join(", "))
1361            }
1362            Self::List { items, .. } => {
1363                let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
1364                format!("[{}]", xs.join(", "))
1365            }
1366            Self::Try { body, handlers, .. } => {
1367                let hs: Vec<String> = handlers
1368                    .iter()
1369                    .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
1370                    .collect();
1371                format!("try {} catch {}", body.render(), hs.join("; "))
1372            }
1373            Self::OperatorRef { op, .. } => format!("({op})"),
1374            Self::LeftSection { op, operand, .. } => format!("({} {})", operand.render(), op),
1375            Self::RightSection { op, operand, .. } => format!("({} {})", op, operand.render()),
1376            Self::Error { raw, .. } => raw.clone(),
1377        }
1378    }
1379
1380    /// Render with parentheses if this expression wouldn't survive as an
1381    /// application argument.
1382    fn render_atomic(&self) -> String {
1383        match self {
1384            Self::Var { .. }
1385            | Self::Con { .. }
1386            | Self::Lit { .. }
1387            | Self::Tuple { .. }
1388            | Self::List { .. }
1389            | Self::OperatorRef { .. }
1390            | Self::LeftSection { .. }
1391            | Self::RightSection { .. }
1392            | Self::Error { .. } => self.render(),
1393            _ => format!("({})", self.render()),
1394        }
1395    }
1396
1397    /// The head of an application spine: for `Foo.exercise cid X`, the
1398    /// `Foo.exercise` Var. For non-apps, the expression itself.
1399    #[must_use]
1400    pub fn application_head(&self) -> &Self {
1401        match self {
1402            Self::App { func, .. } => func.application_head(),
1403            _ => self,
1404        }
1405    }
1406
1407    /// Application arguments, empty for non-apps. The `App` spine is flattened
1408    /// (see the [`App`](Self::App) variant), so for `f a b c` this returns all
1409    /// three arguments `[a, b, c]`, not a single curried layer.
1410    #[must_use]
1411    pub fn application_args(&self) -> &[Self] {
1412        match self {
1413            Self::App { args, .. } => args,
1414            _ => &[],
1415        }
1416    }
1417}
1418
1419fn render_do_stmt(s: &DoStmt) -> String {
1420    match s {
1421        DoStmt::Bind { pat, expr, .. } => format!("{} <- {}", pat.render(), expr.render()),
1422        DoStmt::Let { bindings, .. } => {
1423            let bs: Vec<String> = bindings.iter().map(render_binding).collect();
1424            format!("let {}", bs.join("; "))
1425        }
1426        DoStmt::Expr { expr, .. } => expr.render(),
1427    }
1428}
1429
1430fn render_binding(b: &Binding) -> String {
1431    let mut s = b.pat.render();
1432    for p in &b.params {
1433        s.push(' ');
1434        s.push_str(&p.render());
1435    }
1436    format!("{} = {}", s, b.expr.render())
1437}
1438
1439impl Pat {
1440    #[must_use]
1441    pub const fn pos(&self) -> Pos {
1442        match self {
1443            Self::Var { pos, .. }
1444            | Self::Wild { pos, .. }
1445            | Self::Con { pos, .. }
1446            | Self::Tuple { pos, .. }
1447            | Self::List { pos, .. }
1448            | Self::Lit { pos, .. }
1449            | Self::As { pos, .. }
1450            | Self::Other { pos, .. } => *pos,
1451        }
1452    }
1453
1454    /// Byte span covering the whole pattern.
1455    #[must_use]
1456    pub const fn span(&self) -> Span {
1457        match self {
1458            Self::Var { span, .. }
1459            | Self::Wild { span, .. }
1460            | Self::Con { span, .. }
1461            | Self::Tuple { span, .. }
1462            | Self::List { span, .. }
1463            | Self::Lit { span, .. }
1464            | Self::As { span, .. }
1465            | Self::Other { span, .. } => *span,
1466        }
1467    }
1468
1469    /// Render back to compact, source-*like* text. Lossy and normalizing in the
1470    /// same way as [`Expr::render`]; use the node's [`span`](Self::span) for
1471    /// byte-faithful text.
1472    #[must_use]
1473    pub fn render(&self) -> String {
1474        match self {
1475            Self::Var { name, .. } => name.to_string(),
1476            Self::Wild { .. } => "_".to_string(),
1477            Self::Con {
1478                qualifier,
1479                name,
1480                args,
1481                ..
1482            } => {
1483                let head = qualifier
1484                    .as_ref()
1485                    .map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
1486                if args.is_empty() {
1487                    head
1488                } else {
1489                    let parts: Vec<String> = args.iter().map(|p| p.render()).collect();
1490                    format!("({} {})", head, parts.join(" "))
1491                }
1492            }
1493            Self::Tuple { items, .. } => {
1494                let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
1495                format!("({})", xs.join(", "))
1496            }
1497            Self::List { items, .. } => {
1498                let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
1499                format!("[{}]", xs.join(", "))
1500            }
1501            Self::Lit { kind, text, .. } => match kind {
1502                LitKind::Text => format!("{text:?}"),
1503                LitKind::Char => format!("'{text}'"),
1504                _ => text.clone(),
1505            },
1506            Self::As { name, pat, .. } => format!("{}@{}", name, pat.render()),
1507            Self::Other { raw, .. } => raw.clone(),
1508        }
1509    }
1510}
1511
1512impl std::fmt::Display for Expr {
1513    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1514        f.write_str(&self.render())
1515    }
1516}
1517
1518impl std::fmt::Display for Pat {
1519    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1520        f.write_str(&self.render())
1521    }
1522}
1523
1524// Span invariants for the lossless AST tile layer; render-shape tests live in integration tests.
1525#[cfg(test)]
1526mod tests {
1527    use super::*;
1528
1529    fn span(start: usize, end: usize) -> Span {
1530        Span::from_usize(start, end)
1531    }
1532
1533    #[test]
1534    fn span_distinguishes_empty_from_invalid() {
1535        assert!(span(3, 3).is_valid());
1536        assert!(span(3, 3).is_empty());
1537
1538        assert!(!span(4, 3).is_valid());
1539        assert!(!span(4, 3).is_empty());
1540    }
1541
1542    #[test]
1543    fn contains_rejects_invalid_spans() {
1544        let parent = span(1, 10);
1545
1546        assert!(parent.contains(&span(3, 7)));
1547        assert!(!parent.contains(&span(7, 3)));
1548        assert!(!span(10, 1).contains(&span(3, 7)));
1549    }
1550
1551    #[test]
1552    fn span_range_and_get_share_source_bytes_safely() {
1553        let source = "foo: Int";
1554
1555        assert_eq!(span(0, 3).range(), 0..3);
1556        assert_eq!(span(0, 3).get(source), Some("foo"));
1557        assert_eq!(span(3, 7).get(source), Some(": In"));
1558        assert!(span(3, 100).get(source).is_none());
1559    }
1560}