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//! Every node carries a source position and byte span. Downstream crates
5//! consume this tree directly: daml-fmt re-prints layout from the spans, and
6//! daml-lint lowers it onto its own rule-facing IR.
7
8pub use crate::lexer::{Identifier, ModuleName, Operator, Pos};
9
10/// Byte span of an AST node.
11///
12/// `[start, end)` into the original source, same basis as `Token::start`/
13/// `Token::end`. Covers every (non-virtual) token that belongs to the node —
14/// first token's `start` to last token's `end`.
15///
16/// Invariants the parser maintains (checked over the corpus by
17/// `render_from_ast`): a child's span is contained in its parent's span, and
18/// sibling spans are ordered and non-overlapping. Trivia (comments, blank
19/// lines) live *between* sibling spans.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub struct Span {
22    pub start: usize,
23    pub end: usize,
24}
25
26impl Span {
27    #[must_use]
28    pub const fn new(start: usize, end: usize) -> Self {
29        Self { start, end }
30    }
31
32    /// True when the span is well-formed (`start <= end`).
33    #[must_use]
34    pub const fn is_valid(&self) -> bool {
35        self.start <= self.end
36    }
37
38    /// True for a zero-width but still valid span.
39    #[must_use]
40    pub const fn is_empty(&self) -> bool {
41        self.start == self.end
42    }
43
44    #[must_use]
45    pub const fn range(&self) -> std::ops::Range<usize> {
46        self.start..self.end
47    }
48
49    #[must_use]
50    pub fn get<'a>(&self, source: &'a str) -> Option<&'a str> {
51        if self.start <= self.end
52            && self.end <= source.len()
53            && source.is_char_boundary(self.start)
54            && source.is_char_boundary(self.end)
55        {
56            Some(&source[self.start..self.end])
57        } else {
58            None
59        }
60    }
61
62    /// `self` fully contains `other`.
63    #[must_use]
64    pub const fn contains(&self, other: &Self) -> bool {
65        self.is_valid() && other.is_valid() && self.start <= other.start && other.end <= self.end
66    }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70#[non_exhaustive]
71pub enum LitKind {
72    Int,
73    Decimal,
74    Text,
75    Char,
76}
77
78/// Side of an operator section.
79///
80/// `(+ 1)` stores `SectionSide::Right`, while `(1 +)` stores
81/// `SectionSide::Left`.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83#[non_exhaustive]
84pub enum SectionSide {
85    /// Right section: operator followed by right operand.
86    Right,
87    /// Left section: left operand followed by operator.
88    Left,
89}
90
91/// Import syntax style.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[non_exhaustive]
94pub enum ImportStyle {
95    /// Qualified import (`import qualified Foo.Bar`, `import Foo.Bar qualified`).
96    Qualified,
97    /// Unqualified import (`import Foo.Bar`, `import Foo.Bar as Baz`).
98    Unqualified,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct FieldAssign {
103    pub name: Identifier,
104    /// None for record puns (`Foo with owner` meaning `owner = owner`)
105    /// and `..` wildcards.
106    pub value: Option<Expr>,
107    pub pos: Pos,
108    pub span: Span,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct Alt {
113    pub pat: Pat,
114    pub body: Expr,
115    pub pos: Pos,
116    pub span: Span,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct Binding {
121    /// Left-hand side: a variable with parameters, or a destructuring pattern.
122    pub pat: Pat,
123    /// Parameter patterns when the LHS is a function binding (`f x y = ...`).
124    pub params: Vec<Pat>,
125    pub expr: Expr,
126    pub pos: Pos,
127    pub span: Span,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum Pat {
133    Var {
134        name: Identifier,
135        pos: Pos,
136        span: Span,
137    },
138    Wild {
139        pos: Pos,
140        span: Span,
141    },
142    Con {
143        qualifier: Option<ModuleName>,
144        name: Identifier,
145        args: Vec<Self>,
146        pos: Pos,
147        span: Span,
148    },
149    Tuple {
150        items: Vec<Self>,
151        pos: Pos,
152        span: Span,
153    },
154    List {
155        items: Vec<Self>,
156        pos: Pos,
157        span: Span,
158    },
159    Lit {
160        kind: LitKind,
161        text: String,
162        pos: Pos,
163        span: Span,
164    },
165    /// `name@pat`
166    As {
167        name: Identifier,
168        pat: Box<Self>,
169        pos: Pos,
170        span: Span,
171    },
172    /// Anything the parser couldn't classify; raw text preserved.
173    Other {
174        raw: String,
175        pos: Pos,
176        span: Span,
177    },
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181#[non_exhaustive]
182pub enum Expr {
183    /// Lowercase variable reference, possibly qualified.
184    Var {
185        qualifier: Option<ModuleName>,
186        name: Identifier,
187        pos: Pos,
188        span: Span,
189    },
190    /// Constructor / data-constructor reference, possibly qualified.
191    Con {
192        qualifier: Option<ModuleName>,
193        name: Identifier,
194        pos: Pos,
195        span: Span,
196    },
197    Lit {
198        kind: LitKind,
199        text: String,
200        pos: Pos,
201        span: Span,
202    },
203    /// Application, flattened: `f a b c` is one App with three args.
204    App {
205        func: Box<Self>,
206        args: Vec<Self>,
207        pos: Pos,
208        span: Span,
209    },
210    /// Binary operator application with source-level operator text.
211    BinOp {
212        op: Operator,
213        lhs: Box<Self>,
214        rhs: Box<Self>,
215        pos: Pos,
216        span: Span,
217    },
218    /// Unary negation.
219    Neg {
220        expr: Box<Self>,
221        pos: Pos,
222        span: Span,
223    },
224    Lambda {
225        params: Vec<Pat>,
226        body: Box<Self>,
227        pos: Pos,
228        span: Span,
229    },
230    If {
231        cond: Box<Self>,
232        then_branch: Box<Self>,
233        else_branch: Box<Self>,
234        pos: Pos,
235        span: Span,
236    },
237    Case {
238        scrutinee: Box<Self>,
239        alts: Vec<Alt>,
240        pos: Pos,
241        span: Span,
242    },
243    Do {
244        stmts: Vec<DoStmt>,
245        pos: Pos,
246        span: Span,
247    },
248    LetIn {
249        bindings: Vec<Binding>,
250        body: Box<Self>,
251        pos: Pos,
252        span: Span,
253    },
254    /// `base with f = e, ...` — record construction when base is a Con,
255    /// record update otherwise.
256    Record {
257        base: Box<Self>,
258        fields: Vec<FieldAssign>,
259        pos: Pos,
260        span: Span,
261    },
262    Tuple {
263        items: Vec<Self>,
264        pos: Pos,
265        span: Span,
266    },
267    List {
268        items: Vec<Self>,
269        pos: Pos,
270        span: Span,
271    },
272    /// `try <body> catch <alts>`
273    Try {
274        body: Box<Self>,
275        handlers: Vec<Alt>,
276        pos: Pos,
277        span: Span,
278    },
279    /// Right operator section like `(+ 1)` / left section `(1 +)`.
280    Section {
281        op: Operator,
282        operand: Option<Box<Self>>,
283        side: SectionSide,
284        pos: Pos,
285        span: Span,
286    },
287    /// Expression the parser could not understand; raw text preserved so
288    /// a parse failure degrades to the shim's behavior instead of dying.
289    Error { raw: String, pos: Pos, span: Span },
290}
291
292#[derive(Debug, Clone, PartialEq, Eq)]
293#[non_exhaustive]
294pub enum DoStmt {
295    /// `pat <- expr`
296    Bind {
297        pat: Pat,
298        expr: Expr,
299        pos: Pos,
300        span: Span,
301    },
302    /// `let x = e` (no `in`) inside a do block.
303    Let {
304        bindings: Vec<Binding>,
305        pos: Pos,
306        span: Span,
307    },
308    /// Bare expression statement.
309    Expr { expr: Expr, pos: Pos, span: Span },
310}
311
312/// Structured Daml type, parsed from the real token stream.
313///
314/// Scoped to the forms the corpus actually contains; it exists so consumers can
315/// tell a type *application* from a *function arrow* from an
316/// atomic constructor — a distinction a string matcher structurally cannot make.
317/// Every node carries a byte span so consumers can render exact source text from
318/// `(source, span)`.
319#[derive(Debug, Clone)]
320#[non_exhaustive]
321pub enum Type {
322    /// Type constructor, possibly qualified: `Party`, `DA.Map.Map`.
323    Con {
324        qualifier: Option<ModuleName>,
325        name: Identifier,
326        span: Span,
327    },
328    /// Type application, head applied to one or more args: `ContractId Foo`,
329    /// `Map Text Int`, `Script ()`. Type-level nat literals (the `10` in
330    /// `Numeric 10`) are NOT types, so they are dropped from the arg list — a
331    /// `Numeric 10` collapses to the bare head `Con "Numeric"`.
332    App(Box<Self>, Vec<Self>, Span),
333    /// List type `[T]`.
334    List(Box<Self>, Span),
335    /// Tuple type `(a, b, ...)`.
336    Tuple(Vec<Self>, Span),
337    /// Function type `a -> b` (right-associative).
338    Fun(Box<Self>, Box<Self>, Span),
339    /// Lowercase type variable: `a`, `n`.
340    Var(Identifier, Span),
341    /// The unit type `()`.
342    Unit(Span),
343    /// A constrained type `C a => T`: the context is not modeled, the body `T`
344    /// is kept.
345    Constrained(Box<Self>, Span),
346}
347
348/// Type equality intentionally ignores source spans.
349///
350/// Spans describe where equivalent type syntax appeared in a source file; they
351/// are not part of structural type identity used by parser consumers.
352impl PartialEq for Type {
353    fn eq(&self, other: &Self) -> bool {
354        match (self, other) {
355            (
356                Self::Con {
357                    qualifier: aq,
358                    name: an,
359                    ..
360                },
361                Self::Con {
362                    qualifier: bq,
363                    name: bn,
364                    ..
365                },
366            ) => aq == bq && an == bn,
367            (Self::App(ah, aa, _), Self::App(bh, ba, _)) => ah == bh && aa == ba,
368            (Self::List(a, _), Self::List(b, _))
369            | (Self::Constrained(a, _), Self::Constrained(b, _)) => a == b,
370            (Self::Tuple(a, _), Self::Tuple(b, _)) => a == b,
371            (Self::Fun(al, ar, _), Self::Fun(bl, br, _)) => al == bl && ar == br,
372            (Self::Var(a, _), Self::Var(b, _)) => a == b,
373            (Self::Unit(_), Self::Unit(_)) => true,
374            _ => false,
375        }
376    }
377}
378
379impl Eq for Type {}
380
381impl Type {
382    #[must_use]
383    pub const fn span(&self) -> Span {
384        match self {
385            Self::Con { span, .. }
386            | Self::App(_, _, span)
387            | Self::List(_, span)
388            | Self::Tuple(_, span)
389            | Self::Fun(_, _, span)
390            | Self::Var(_, span)
391            | Self::Unit(span)
392            | Self::Constrained(_, span) => *span,
393        }
394    }
395
396    pub(crate) const fn with_span(mut self, span: Span) -> Self {
397        match &mut self {
398            Self::Con { span: s, .. }
399            | Self::App(_, _, s)
400            | Self::List(_, s)
401            | Self::Tuple(_, s)
402            | Self::Fun(_, _, s)
403            | Self::Var(_, s)
404            | Self::Unit(s)
405            | Self::Constrained(_, s) => *s = span,
406        }
407        self
408    }
409}
410
411#[derive(Debug, Clone, PartialEq, Eq)]
412pub struct FieldDecl {
413    pub name: Identifier,
414    /// Structured field type parsed from the token stream. `None` when the type
415    /// could not be parsed cleanly (analysis treats it as unknown).
416    pub ty: Option<Type>,
417    pub pos: Pos,
418    pub span: Span,
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422pub enum Consuming {
423    Consuming,
424    NonConsuming,
425    PreConsuming,
426    PostConsuming,
427}
428
429#[derive(Debug, Clone, PartialEq, Eq)]
430pub struct ChoiceDecl {
431    pub name: Identifier,
432    pub consuming: Consuming,
433    /// Structured return type. `None` if it could not be parsed cleanly or the
434    /// choice declared no return type.
435    pub return_ty: Option<Type>,
436    pub params: Vec<FieldDecl>,
437    /// Comma-separated controller expressions.
438    pub controllers: Vec<Expr>,
439    /// Choice observers, if any.
440    pub observers: Vec<Expr>,
441    pub body: Option<Expr>,
442    pub pos: Pos,
443    pub span: Span,
444}
445
446#[derive(Debug, Clone, PartialEq, Eq)]
447pub enum TemplateBodyDecl {
448    Signatory {
449        parties: Vec<Expr>,
450        pos: Pos,
451        span: Span,
452    },
453    Observer {
454        parties: Vec<Expr>,
455        pos: Pos,
456        span: Span,
457    },
458    Ensure {
459        expr: Expr,
460        pos: Pos,
461        span: Span,
462    },
463    Key {
464        expr: Expr,
465        /// Structured key type. `None` if absent or not cleanly parseable.
466        ty: Option<Type>,
467        pos: Pos,
468        span: Span,
469    },
470    Maintainer {
471        expr: Expr,
472        pos: Pos,
473        span: Span,
474    },
475    Choice(ChoiceDecl),
476    InterfaceInstance(InterfaceInstanceDecl),
477    /// `agreement`, `let` blocks, deprecated `controller ... can`, etc.
478    Other {
479        raw: String,
480        pos: Pos,
481        span: Span,
482    },
483}
484
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub struct InterfaceInstanceDecl {
487    /// Interface being implemented (`Disclosure.I`).
488    pub interface_name: ModuleName,
489    /// Template it is for (from `for Foo`); the enclosing template when
490    /// declared inside one.
491    pub for_template: ModuleName,
492    /// Method implementations: name → bound expression.
493    pub methods: Vec<Binding>,
494    pub pos: Pos,
495    pub span: Span,
496}
497
498#[derive(Debug, Clone, PartialEq, Eq)]
499pub struct TemplateDecl {
500    pub name: Identifier,
501    pub fields: Vec<FieldDecl>,
502    pub body: Vec<TemplateBodyDecl>,
503    pub pos: Pos,
504    pub span: Span,
505}
506
507#[derive(Debug, Clone, PartialEq, Eq)]
508pub struct InterfaceDecl {
509    pub name: Identifier,
510    /// Interfaces this interface requires (`requires Lockable.I, ...`).
511    pub requires: Vec<ModuleName>,
512    pub viewtype: Option<ModuleName>,
513    /// Method signatures: name and type text.
514    pub methods: Vec<FieldDecl>,
515    pub choices: Vec<ChoiceDecl>,
516    pub pos: Pos,
517    pub span: Span,
518}
519
520#[derive(Debug, Clone, PartialEq, Eq)]
521pub struct Equation {
522    pub params: Vec<Pat>,
523    pub body: Expr,
524    /// Guarded equations keep their guards as (guard, body) pairs; `body`
525    /// then holds the first guarded body for convenience.
526    pub guards: Vec<(Expr, Expr)>,
527    /// `where` helper bindings attached to this equation.
528    pub where_bindings: Vec<Binding>,
529    pub pos: Pos,
530    pub span: Span,
531}
532
533#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct FunctionDecl {
535    pub name: Identifier,
536    pub ty: Option<Type>,
537    pub equations: Vec<Equation>,
538    pub pos: Pos,
539    /// Span of the function's first appearance (signature or first equation).
540    /// Convenience anchor; a multi-equation function's precise ranges are the
541    /// per-`Equation` spans, since equations need not be contiguous in source.
542    pub span: Span,
543    /// Span of the standalone type signature `name : Type`, if one was seen.
544    pub sig_span: Option<Span>,
545}
546
547#[derive(Debug, Clone, PartialEq, Eq)]
548pub struct ImportDecl {
549    pub module_name: ModuleName,
550    pub style: ImportStyle,
551    pub alias: Option<ModuleName>,
552    pub pos: Pos,
553    pub span: Span,
554}
555
556#[derive(Debug, Clone, PartialEq, Eq)]
557#[non_exhaustive]
558pub enum Decl {
559    Template(TemplateDecl),
560    Interface(InterfaceDecl),
561    Function(FunctionDecl),
562    /// data/type/class/instance/exception — recorded with name + span.
563    TypeDef {
564        keyword: String,
565        name: Identifier,
566        pos: Pos,
567        span: Span,
568    },
569    /// Anything unparseable at the top level (diagnostic already emitted).
570    Unknown {
571        raw: String,
572        pos: Pos,
573        span: Span,
574    },
575}
576
577#[derive(Debug, Clone, PartialEq, Eq)]
578pub struct Module {
579    pub name: ModuleName,
580    pub pos: Pos,
581    /// Whole-module extent: `[0, source.len())`. Container for all decls.
582    pub span: Span,
583    /// Span of the `module M (...) where` header clause; empty when the file
584    /// has no module header. Lets the span oracle treat header tokens as
585    /// covered without a dedicated header node.
586    pub header: Span,
587    pub imports: Vec<ImportDecl>,
588    pub decls: Vec<Decl>,
589}
590
591/// Why a [`ParseDiagnostic`] fired.
592///
593/// Lets a consumer separate syntax the parser deliberately does not model (still
594/// safe, just unanalyzed) from a genuine malformation, a recursion-limit
595/// degradation, or a lexical error.
596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597#[non_exhaustive]
598pub enum DiagnosticCategory {
599    /// A whole declaration could not be parsed and was skipped to the next item.
600    SkippedDecl,
601    /// A malformed expression, pattern, or expected-token error inside an
602    /// otherwise-recognized construct.
603    Malformed,
604    /// A construct the parser intentionally does not support, e.g. legacy
605    /// `controller ... can` choice syntax.
606    UnsupportedSyntax,
607    /// Expression/pattern nesting exceeded the recursion bound and was degraded
608    /// to raw text.
609    RecursionLimit,
610    /// A lexical error (unterminated string/comment, stray character).
611    Lex,
612}
613
614impl DiagnosticCategory {
615    /// Stable kebab-case tag for machine-readable output (JSON/SARIF) and logs.
616    #[must_use]
617    pub const fn as_str(self) -> &'static str {
618        match self {
619            Self::SkippedDecl => "skipped-declaration",
620            Self::Malformed => "malformed",
621            Self::UnsupportedSyntax => "unsupported-syntax",
622            Self::RecursionLimit => "recursion-limit",
623            Self::Lex => "lexical-error",
624        }
625    }
626}
627
628/// Parse diagnostic — never fatal; the scan continues.
629#[derive(Debug, Clone, PartialEq, Eq)]
630pub struct ParseDiagnostic {
631    pub message: String,
632    pub pos: Pos,
633    /// Byte span of the offending region. The end is the actionable addition
634    /// over `pos`-alone; zero-width when only a point is known (lex errors,
635    /// EOF).
636    pub span: Span,
637    pub category: DiagnosticCategory,
638}
639
640impl Expr {
641    #[must_use]
642    pub const fn pos(&self) -> Pos {
643        match self {
644            Self::Var { pos, .. }
645            | Self::Con { pos, .. }
646            | Self::Lit { pos, .. }
647            | Self::App { pos, .. }
648            | Self::BinOp { pos, .. }
649            | Self::Neg { pos, .. }
650            | Self::Lambda { pos, .. }
651            | Self::If { pos, .. }
652            | Self::Case { pos, .. }
653            | Self::Do { pos, .. }
654            | Self::LetIn { pos, .. }
655            | Self::Record { pos, .. }
656            | Self::Tuple { pos, .. }
657            | Self::List { pos, .. }
658            | Self::Try { pos, .. }
659            | Self::Section { pos, .. }
660            | Self::Error { pos, .. } => *pos,
661        }
662    }
663
664    /// Byte span covering the whole expression.
665    #[must_use]
666    pub const fn span(&self) -> Span {
667        match self {
668            Self::Var { span, .. }
669            | Self::Con { span, .. }
670            | Self::Lit { span, .. }
671            | Self::App { span, .. }
672            | Self::BinOp { span, .. }
673            | Self::Neg { span, .. }
674            | Self::Lambda { span, .. }
675            | Self::If { span, .. }
676            | Self::Case { span, .. }
677            | Self::Do { span, .. }
678            | Self::LetIn { span, .. }
679            | Self::Record { span, .. }
680            | Self::Tuple { span, .. }
681            | Self::List { span, .. }
682            | Self::Try { span, .. }
683            | Self::Section { span, .. }
684            | Self::Error { span, .. } => *span,
685        }
686    }
687
688    /// Render back to compact, source-*like* text for diagnostics and `raw`
689    /// fields.
690    ///
691    /// This is **lossy and normalizing**, not byte-faithful: original layout is
692    /// dropped (e.g. `do`/`let` statements are joined with `; `), operators and
693    /// spacing are normalized, and comments/trivia are gone. Use it for a quick
694    /// human-readable echo of an expression; for source-exact reconstruction use
695    /// the node's [`span`](Self::span) into the original text (that is how
696    /// `daml-fmt` and [`crate::ast_span::render_from_ast`] stay lossless).
697    #[must_use]
698    pub fn render(&self) -> String {
699        match self {
700            Self::Var {
701                qualifier, name, ..
702            }
703            | Self::Con {
704                qualifier, name, ..
705            } => qualifier
706                .as_ref()
707                .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
708            Self::Lit { kind, text, .. } => match kind {
709                LitKind::Text => format!("{text:?}"),
710                LitKind::Char => format!("'{text}'"),
711                _ => text.clone(),
712            },
713            Self::App { func, args, .. } => {
714                let mut s = func.render_atomic();
715                for a in args {
716                    s.push(' ');
717                    s.push_str(&a.render_atomic());
718                }
719                s
720            }
721            Self::BinOp { op, lhs, rhs, .. } => {
722                if *op == "." {
723                    // Record projection / composition: `account.custodian`.
724                    format!("{}.{}", lhs.render_atomic(), rhs.render_atomic())
725                } else {
726                    format!("{} {} {}", lhs.render_atomic(), op, rhs.render_atomic())
727                }
728            }
729            Self::Neg { expr, .. } => format!("-{}", expr.render_atomic()),
730            Self::Lambda { params, body, .. } => {
731                let ps: Vec<String> = params.iter().map(|p| p.render()).collect();
732                format!("\\{} -> {}", ps.join(" "), body.render())
733            }
734            Self::If {
735                cond,
736                then_branch,
737                else_branch,
738                ..
739            } => format!(
740                "if {} then {} else {}",
741                cond.render(),
742                then_branch.render(),
743                else_branch.render()
744            ),
745            Self::Case {
746                scrutinee, alts, ..
747            } => {
748                let arms: Vec<String> = alts
749                    .iter()
750                    .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
751                    .collect();
752                format!("case {} of {}", scrutinee.render(), arms.join("; "))
753            }
754            Self::Do { stmts, .. } => {
755                let body: Vec<String> = stmts.iter().map(render_do_stmt).collect();
756                format!("do {}", body.join("; "))
757            }
758            Self::LetIn { bindings, body, .. } => {
759                let bs: Vec<String> = bindings.iter().map(render_binding).collect();
760                format!("let {} in {}", bs.join("; "), body.render())
761            }
762            Self::Record { base, fields, .. } => {
763                let fs: Vec<String> = fields
764                    .iter()
765                    .map(|f| {
766                        f.value.as_ref().map_or_else(
767                            || f.name.to_string(),
768                            |v| format!("{} = {}", f.name, v.render()),
769                        )
770                    })
771                    .collect();
772                format!("{} with {}", base.render_atomic(), fs.join("; "))
773            }
774            Self::Tuple { items, .. } => {
775                let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
776                format!("({})", xs.join(", "))
777            }
778            Self::List { items, .. } => {
779                let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
780                format!("[{}]", xs.join(", "))
781            }
782            Self::Try { body, handlers, .. } => {
783                let hs: Vec<String> = handlers
784                    .iter()
785                    .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
786                    .collect();
787                format!("try {} catch {}", body.render(), hs.join("; "))
788            }
789            Self::Section {
790                op, operand, side, ..
791            } => match (operand, side) {
792                (Some(e), SectionSide::Left) => format!("({} {})", e.render(), op),
793                (Some(e), SectionSide::Right) => format!("({} {})", op, e.render()),
794                (None, _) => format!("({op})"),
795            },
796            Self::Error { raw, .. } => raw.clone(),
797        }
798    }
799
800    /// Render with parentheses if this expression wouldn't survive as an
801    /// application argument.
802    fn render_atomic(&self) -> String {
803        match self {
804            Self::Var { .. }
805            | Self::Con { .. }
806            | Self::Lit { .. }
807            | Self::Tuple { .. }
808            | Self::List { .. }
809            | Self::Section { .. }
810            | Self::Error { .. } => self.render(),
811            _ => format!("({})", self.render()),
812        }
813    }
814
815    /// The head of an application spine: for `Foo.exercise cid X`, the
816    /// `Foo.exercise` Var. For non-apps, the expression itself.
817    #[must_use]
818    pub fn application_head(&self) -> &Self {
819        match self {
820            Self::App { func, .. } => func.application_head(),
821            _ => self,
822        }
823    }
824
825    /// Application arguments, empty for non-apps. The `App` spine is flattened
826    /// (see the [`App`](Self::App) variant), so for `f a b c` this returns all
827    /// three arguments `[a, b, c]`, not a single curried layer.
828    #[must_use]
829    pub fn application_args(&self) -> &[Self] {
830        match self {
831            Self::App { args, .. } => args,
832            _ => &[],
833        }
834    }
835}
836
837fn render_do_stmt(s: &DoStmt) -> String {
838    match s {
839        DoStmt::Bind { pat, expr, .. } => format!("{} <- {}", pat.render(), expr.render()),
840        DoStmt::Let { bindings, .. } => {
841            let bs: Vec<String> = bindings.iter().map(render_binding).collect();
842            format!("let {}", bs.join("; "))
843        }
844        DoStmt::Expr { expr, .. } => expr.render(),
845    }
846}
847
848fn render_binding(b: &Binding) -> String {
849    let mut s = b.pat.render();
850    for p in &b.params {
851        s.push(' ');
852        s.push_str(&p.render());
853    }
854    format!("{} = {}", s, b.expr.render())
855}
856
857impl Pat {
858    #[must_use]
859    pub const fn pos(&self) -> Pos {
860        match self {
861            Self::Var { pos, .. }
862            | Self::Wild { pos, .. }
863            | Self::Con { pos, .. }
864            | Self::Tuple { pos, .. }
865            | Self::List { pos, .. }
866            | Self::Lit { pos, .. }
867            | Self::As { pos, .. }
868            | Self::Other { pos, .. } => *pos,
869        }
870    }
871
872    /// Byte span covering the whole pattern.
873    #[must_use]
874    pub const fn span(&self) -> Span {
875        match self {
876            Self::Var { span, .. }
877            | Self::Wild { span, .. }
878            | Self::Con { span, .. }
879            | Self::Tuple { span, .. }
880            | Self::List { span, .. }
881            | Self::Lit { span, .. }
882            | Self::As { span, .. }
883            | Self::Other { span, .. } => *span,
884        }
885    }
886
887    /// Render back to compact, source-*like* text. Lossy and normalizing in the
888    /// same way as [`Expr::render`]; use the node's [`span`](Self::span) for
889    /// byte-faithful text.
890    #[must_use]
891    pub fn render(&self) -> String {
892        match self {
893            Self::Var { name, .. } => name.to_string(),
894            Self::Wild { .. } => "_".to_string(),
895            Self::Con {
896                qualifier,
897                name,
898                args,
899                ..
900            } => {
901                let head = qualifier
902                    .as_ref()
903                    .map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
904                if args.is_empty() {
905                    head
906                } else {
907                    let parts: Vec<String> = args.iter().map(|p| p.render()).collect();
908                    format!("({} {})", head, parts.join(" "))
909                }
910            }
911            Self::Tuple { items, .. } => {
912                let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
913                format!("({})", xs.join(", "))
914            }
915            Self::List { items, .. } => {
916                let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
917                format!("[{}]", xs.join(", "))
918            }
919            Self::Lit { kind, text, .. } => match kind {
920                LitKind::Text => format!("{text:?}"),
921                LitKind::Char => format!("'{text}'"),
922                _ => text.clone(),
923            },
924            Self::As { name, pat, .. } => format!("{}@{}", name, pat.render()),
925            Self::Other { raw, .. } => raw.clone(),
926        }
927    }
928}
929
930impl std::fmt::Display for Expr {
931    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
932        f.write_str(&self.render())
933    }
934}
935
936impl std::fmt::Display for Pat {
937    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
938        f.write_str(&self.render())
939    }
940}
941
942#[cfg(test)]
943mod tests {
944    use super::*;
945
946    fn pos() -> Pos {
947        Pos { line: 1, column: 1 }
948    }
949
950    fn span(start: usize, end: usize) -> Span {
951        Span::new(start, end)
952    }
953
954    #[test]
955    fn span_distinguishes_empty_from_invalid() {
956        assert!(span(3, 3).is_valid());
957        assert!(span(3, 3).is_empty());
958
959        assert!(!span(4, 3).is_valid());
960        assert!(!span(4, 3).is_empty());
961    }
962
963    #[test]
964    fn contains_rejects_invalid_spans() {
965        let parent = span(1, 10);
966
967        assert!(parent.contains(&span(3, 7)));
968        assert!(!parent.contains(&span(7, 3)));
969        assert!(!span(10, 1).contains(&span(3, 7)));
970    }
971
972    #[test]
973    fn span_range_and_get_share_source_bytes_safely() {
974        let source = "foo: Int";
975
976        assert_eq!(span(0, 3).range(), 0..3);
977        assert_eq!(span(0, 3).get(source), Some("foo"));
978        assert_eq!(span(3, 7).get(source), Some(": In"));
979        assert!(span(3, 100).get(source).is_none());
980    }
981
982    #[test]
983    fn expr_render_keeps_normalized_application_and_projection_shape() {
984        let projection = Expr::BinOp {
985            op: ".".into(),
986            lhs: Box::new(Expr::Var {
987                qualifier: None,
988                name: "this".into(),
989                pos: pos(),
990                span: span(0, 4),
991            }),
992            rhs: Box::new(Expr::Var {
993                qualifier: None,
994                name: "note".into(),
995                pos: pos(),
996                span: span(5, 9),
997            }),
998            pos: pos(),
999            span: span(0, 9),
1000        };
1001
1002        let expr = Expr::App {
1003            func: Box::new(Expr::Var {
1004                qualifier: None,
1005                name: "length".into(),
1006                pos: pos(),
1007                span: span(0, 6),
1008            }),
1009            args: vec![projection],
1010            pos: pos(),
1011            span: span(0, 16),
1012        };
1013
1014        assert_eq!(expr.render(), "length (this.note)");
1015    }
1016
1017    #[test]
1018    fn section_render_depends_on_section_side() {
1019        let expr_left = Expr::Section {
1020            op: "+".into(),
1021            operand: Some(Box::new(Expr::Var {
1022                qualifier: None,
1023                name: "x".into(),
1024                pos: pos(),
1025                span: span(0, 1),
1026            })),
1027            side: SectionSide::Left,
1028            pos: pos(),
1029            span: span(0, 4),
1030        };
1031        let expr_right = Expr::Section {
1032            op: "+".into(),
1033            operand: Some(Box::new(Expr::Lit {
1034                kind: LitKind::Int,
1035                text: "1".to_string(),
1036                pos: pos(),
1037                span: span(0, 1),
1038            })),
1039            side: SectionSide::Right,
1040            pos: pos(),
1041            span: span(0, 4),
1042        };
1043
1044        assert_eq!(expr_left.render(), "(x +)");
1045        assert_eq!(expr_right.render(), "(+ 1)");
1046    }
1047
1048    #[test]
1049    fn pat_render_preserves_collection_shape() {
1050        let pat = Pat::Tuple {
1051            items: vec![
1052                Pat::Var {
1053                    name: "owner".into(),
1054                    pos: pos(),
1055                    span: span(1, 6),
1056                },
1057                Pat::List {
1058                    items: Vec::new(),
1059                    pos: pos(),
1060                    span: span(8, 10),
1061                },
1062            ],
1063            pos: pos(),
1064            span: span(0, 11),
1065        };
1066
1067        assert_eq!(pat.render(), "(owner, [])");
1068    }
1069}