Skip to main content

daml_parser/
parse.rs

1//! Recursive-descent parser: laid-out token stream → typed AST (src/ast.rs).
2//!
3//! Error recovery is per-declaration: an unparseable declaration becomes
4//! `Decl::Unknown` plus a diagnostic, and parsing continues at the next
5//! virtual semicolon. The parser never panics and never aborts the file.
6
7use crate::ast::*;
8use crate::layout::resolve_layout;
9use crate::lexer::{lex, Pos, Token, TokenKind};
10use std::collections::HashMap;
11
12pub const MAX_RECURSION_DEPTH: u32 = 128;
13
14#[derive(Debug, Clone)]
15pub struct ParseModuleResult {
16    pub module: Module,
17    pub diagnostics: Vec<ParseDiagnostic>,
18}
19
20#[derive(Clone, Copy, Debug)]
21enum DoExpressionMode {
22    Allow,
23    Disallow,
24}
25
26impl DoExpressionMode {
27    const fn allows_do(&self) -> bool {
28        matches!(self, Self::Allow)
29    }
30}
31
32impl ParseModuleResult {
33    #[must_use]
34    pub const fn has_errors(&self) -> bool {
35        !self.diagnostics.is_empty()
36    }
37
38    #[must_use]
39    pub fn into_parts(self) -> (Module, Vec<ParseDiagnostic>) {
40        (self.module, self.diagnostics)
41    }
42}
43
44/// Parse Daml `source` into a [`Module`] plus any [`ParseDiagnostic`]s, in
45/// source order.
46///
47/// This is the crate's entry point. It **never panics and never aborts the
48/// file**: recovery is per-declaration, so an unparseable declaration becomes a
49/// [`Decl::Unknown`] (with a diagnostic) and parsing continues at the next
50/// declaration. A `Module` is therefore always returned, even for badly broken
51/// input — a non-empty diagnostics list signals problems, not a missing tree.
52///
53/// ```
54/// let result = daml_parser::parse::parse_module("module M where\n");
55/// assert_eq!(result.module.name, "M");
56/// assert!(result.diagnostics.is_empty());
57/// ```
58#[must_use]
59pub fn parse_module(source: &str) -> ParseModuleResult {
60    let lexed = lex(source);
61    let tokens = lexed.tokens;
62    let lex_errors = lexed.errors;
63    let tokens = resolve_layout(tokens);
64    let mut p = Parser {
65        toks: tokens,
66        src_len: source.len(),
67        i: 0,
68        depth: 0,
69        diags: lex_errors
70            .into_iter()
71            .map(|e| {
72                let range = e.byte_range_in(source);
73                ParseDiagnostic {
74                    message: e.to_string(),
75                    pos: e.pos,
76                    span: crate::ast::Span::new(range.start, range.end),
77                    category: DiagnosticCategory::Lex,
78                }
79            })
80            .collect(),
81    };
82    let mut module = p.module();
83    module.span = crate::ast::Span::new(0, source.len());
84    ParseModuleResult {
85        module,
86        diagnostics: p.diags,
87    }
88}
89
90struct Parser {
91    toks: Vec<Token>,
92    /// Source byte length — span fallback when a node consumes no real token.
93    src_len: usize,
94    i: usize,
95    diags: Vec<ParseDiagnostic>,
96    /// Expression/pattern recursion depth; bounded so hostile inputs
97    /// (thousands of nested parens) cannot overflow the stack.
98    depth: u32,
99}
100
101impl Parser {
102    /// Byte span of every non-virtual token consumed since token index `from`
103    /// (a function's entry cursor). This is the node's full extent: first real
104    /// token's `start` to last real token's `end`. Virtual layout tokens carry
105    /// no bytes and are skipped, so spans tile the source and never include the
106    /// trailing whitespace a `VRBrace`/`VSemi` sits on.
107    fn node_span(&self, from: usize) -> crate::ast::Span {
108        let mut a = from;
109        while a < self.i && self.toks[a].is_virtual() {
110            a += 1;
111        }
112        let mut b = self.i;
113        while b > a && self.toks[b - 1].is_virtual() {
114            b -= 1;
115        }
116        if a >= b {
117            // No real token consumed (e.g. an empty error node): zero-width
118            // span at the next real byte position so it still nests inside its
119            // parent. Use `a` (past any leading virtual tokens) — `from` itself
120            // may be a virtual token whose byte offset is a meaningless 0.
121            let p = self.byte_at(a);
122            return crate::ast::Span::new(p, p);
123        }
124        crate::ast::Span::new(self.toks[a].start, self.toks[b - 1].end)
125    }
126
127    /// Byte offset where token `i` begins, or the source end past the last one.
128    fn byte_at(&self, i: usize) -> usize {
129        self.toks.get(i).map(|t| t.start).unwrap_or(self.src_len)
130    }
131
132    /// End byte of the last non-virtual token consumed so far — for nodes
133    /// whose start comes from an already-parsed child rather than the entry
134    /// cursor (e.g. `record with { .. }` built around its base expression).
135    fn end_byte(&self) -> usize {
136        let mut b = self.i;
137        while b > 0 && self.toks[b - 1].is_virtual() {
138            b -= 1;
139        }
140        if b == 0 {
141            0
142        } else {
143            self.toks[b - 1].end
144        }
145    }
146}
147
148impl Parser {
149    // ----- cursor primitives -------------------------------------------
150
151    fn peek(&self) -> Option<&TokenKind> {
152        self.toks.get(self.i).map(|t| &t.kind)
153    }
154
155    fn peek_at(&self, n: usize) -> Option<&TokenKind> {
156        self.toks.get(self.i + n).map(|t| &t.kind)
157    }
158
159    fn pos(&self) -> Pos {
160        self.toks
161            .get(self.i)
162            .or_else(|| self.toks.last())
163            .map_or(Pos { line: 1, column: 1 }, |t| t.pos)
164    }
165
166    fn bump(&mut self) -> Option<Token> {
167        let t = self.toks.get(self.i).cloned();
168        if t.is_some() {
169            self.i += 1;
170        }
171        t
172    }
173
174    fn at_keyword(&self, kw: &str) -> bool {
175        self.peek().is_some_and(|t| t.is_keyword(kw))
176    }
177
178    fn eat_keyword(&mut self, kw: &str) -> bool {
179        if self.at_keyword(kw) {
180            self.i += 1;
181            true
182        } else {
183            false
184        }
185    }
186
187    fn at_op(&self, op: &str) -> bool {
188        self.peek().is_some_and(|t| t.is_op(op))
189    }
190
191    fn eat_op(&mut self, op: &str) -> bool {
192        if self.at_op(op) {
193            self.i += 1;
194            true
195        } else {
196            false
197        }
198    }
199
200    fn at(&self, tok: &TokenKind) -> bool {
201        self.peek() == Some(tok)
202    }
203
204    fn eat(&mut self, tok: &TokenKind) -> bool {
205        if self.at(tok) {
206            self.i += 1;
207            true
208        } else {
209            false
210        }
211    }
212
213    /// Emit a `Malformed` diagnostic at the current token (the common case).
214    fn diag(&mut self, message: impl Into<String>) {
215        self.diag_cat(DiagnosticCategory::Malformed, message);
216    }
217
218    /// Emit a diagnostic with an explicit recovery category. The span is the
219    /// current token's byte extent (the offending token), so consumers get an
220    /// end position, not just a start.
221    fn diag_cat(&mut self, category: DiagnosticCategory, message: impl Into<String>) {
222        let pos = self.pos();
223        let span = self.cur_span();
224        self.diags.push(ParseDiagnostic {
225            message: message.into(),
226            pos,
227            span,
228            category,
229        });
230    }
231
232    fn parse_type_annotation(
233        &mut self,
234        type_start: usize,
235        type_end: usize,
236        context: &'static str,
237    ) -> Option<Type> {
238        let type_start = type_start.min(self.toks.len());
239        let type_end = type_end.min(self.toks.len());
240        let tokens = &self.toks[type_start..type_end];
241        let ty = parse_type_from_tokens(tokens).or_else(|| {
242            let trimmed = Self::trim_type_tokens_for_parse(tokens);
243            if trimmed < tokens.len() {
244                parse_type_from_tokens(&tokens[..trimmed])
245            } else {
246                None
247            }
248        });
249        if ty.is_none() {
250            self.diags.push(ParseDiagnostic {
251                message: format!("malformed {context} type annotation"),
252                pos: self.pos_of_token(type_start),
253                span: self.span_of_token_range(type_start, type_end),
254                category: DiagnosticCategory::Malformed,
255            });
256        }
257        ty
258    }
259
260    /// Trim trailing tokens that belong to declaration tails (for example `= expr`
261    /// in typed function signatures, explicit semicolons, or the next field
262    /// declaration) so we can parse valid corpus forms that our tiny type
263    /// parser doesn't model directly.
264    fn trim_type_tokens_for_parse(tokens: &[Token]) -> usize {
265        let mut depth = 0usize;
266        let mut bracket_depth = 0usize;
267        let mut i = 0usize;
268        while i < tokens.len() {
269            match &tokens[i].kind {
270                TokenKind::LParen | TokenKind::LBracket => {
271                    depth += 1;
272                    i += 1;
273                }
274                TokenKind::RParen | TokenKind::RBracket => {
275                    depth = depth.saturating_sub(1);
276                    i += 1;
277                }
278                TokenKind::LBrace => {
279                    bracket_depth += 1;
280                    i += 1;
281                }
282                TokenKind::RBrace => {
283                    bracket_depth = bracket_depth.saturating_sub(1);
284                    i += 1;
285                }
286                TokenKind::Op(o) if o.as_str() == "=" && depth == 0 && bracket_depth == 0 => {
287                    return i;
288                }
289                TokenKind::Semi | TokenKind::VSemi if depth == 0 && bracket_depth == 0 => {
290                    return i;
291                }
292                TokenKind::Comma
293                    if depth == 0
294                        && bracket_depth == 0
295                        && matches!(
296                            tokens.get(i + 1),
297                            Some(Token {
298                                kind: TokenKind::LowerId {
299                                    qualifier: None,
300                                    ..
301                                },
302                                ..
303                            })
304                        )
305                        && matches!(
306                            tokens.get(i + 2),
307                            Some(Token { kind: TokenKind::Op(o), .. }) if o.as_str() == ":"
308                        ) =>
309                {
310                    return i;
311                }
312                _ => {
313                    i += 1;
314                }
315            }
316        }
317        tokens.len()
318    }
319
320    fn pos_of_token(&self, idx: usize) -> Pos {
321        self.toks.get(idx).map_or_else(|| self.pos(), |tok| tok.pos)
322    }
323
324    fn span_of_token_range(&self, start: usize, end: usize) -> Span {
325        let start = start.min(self.toks.len());
326        let end = end.min(self.toks.len());
327        let span_start = self.byte_at(start);
328        if end <= start {
329            return Span::new(span_start, span_start);
330        }
331
332        let mut cursor = end;
333        while cursor > start {
334            cursor -= 1;
335            let token = &self.toks[cursor];
336            if !token.is_virtual() {
337                return Span::new(span_start, token.end);
338            }
339        }
340
341        Span::new(span_start, span_start)
342    }
343
344    /// Byte span of the next real (non-virtual) token, or a zero-width span at
345    /// end-of-input. Used to anchor a diagnostic to the offending token.
346    fn cur_span(&self) -> crate::ast::Span {
347        let mut j = self.i;
348        while self.toks.get(j).is_some_and(|t| t.is_virtual()) {
349            j += 1;
350        }
351        self.toks.get(j).map_or_else(
352            || crate::ast::Span::new(self.src_len, self.src_len),
353            |t| crate::ast::Span::new(t.start, t.end),
354        )
355    }
356
357    /// Skip tokens until the end of the current block item: a `VSemi` or
358    /// `VRBrace` at nesting depth zero (relative to here). Consumes neither.
359    fn skip_to_item_end(&mut self) {
360        let mut depth = 0usize;
361        let mut brackets = 0usize;
362        while let Some(t) = self.peek() {
363            match t {
364                TokenKind::VLBrace => depth += 1,
365                TokenKind::VRBrace => {
366                    if depth == 0 {
367                        return;
368                    }
369                    depth -= 1;
370                }
371                TokenKind::VSemi if depth == 0 && brackets == 0 => return,
372                TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => brackets += 1,
373                TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
374                    if brackets == 0 {
375                        // Closing bracket of an enclosing construct: stop
376                        // before it so the caller can match it.
377                        return;
378                    }
379                    brackets -= 1;
380                }
381                _ => {}
382            }
383            self.i += 1;
384        }
385    }
386
387    /// Raw text of tokens from `start` to the current position.
388    fn slice_text(&self, start: usize) -> String {
389        render_token_slice(&self.toks[start..self.i])
390    }
391
392    // ----- module ------------------------------------------------------
393
394    fn module(&mut self) -> Module {
395        let pos = self.pos();
396        let header_start = self.i;
397        let mut header = crate::ast::Span::new(0, 0);
398        let mut name = ModuleName::from("Unknown");
399
400        if self.eat_keyword("module") {
401            if let Some(TokenKind::UpperId { qualifier, name: n }) = self.peek().cloned() {
402                self.bump();
403                name = match qualifier {
404                    Some(q) => format!("{q}.{n}").into(),
405                    None => n.into(),
406                };
407            }
408            // Optional export list.
409            if self.at(&TokenKind::LParen) {
410                self.skip_balanced_parens();
411            }
412            if !self.eat_keyword("where") {
413                self.diag("expected 'where' after module header");
414            }
415            header = self.node_span(header_start);
416        }
417
418        let mut imports = Vec::new();
419        let mut decls: Vec<Decl> = Vec::new();
420
421        // Consume the opening brace of the module body if present. The result
422        // is unused: the loop below terminates on the matching close brace or
423        // end-of-input regardless of whether the block was braced.
424        let _ = self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace);
425        loop {
426            while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
427            match self.peek() {
428                None => break,
429                Some(TokenKind::VRBrace | TokenKind::RBrace) => {
430                    self.bump();
431                    break;
432                }
433                // A stray closing bracket inside a block is garbage from a
434                // failed item parse — record it as an Unknown declaration so
435                // its bytes stay covered, then continue (skip_to_item_end
436                // deliberately stops before unmatched closers).
437                Some(TokenKind::RParen | TokenKind::RBracket) => {
438                    let cpos = self.pos();
439                    let cstart = self.i;
440                    self.bump();
441                    decls.push(Decl::Unknown {
442                        raw: self.slice_text(cstart),
443                        pos: cpos,
444                        span: self.node_span(cstart),
445                    });
446                    continue;
447                }
448                _ => {}
449            }
450            let before = self.i;
451            self.declaration(&mut imports, &mut decls);
452            if self.i == before {
453                // Defensive: guarantee progress even on a parser bug.
454                self.bump();
455            }
456        }
457
458        merge_functions(&mut decls);
459
460        Module {
461            name,
462            pos,
463            header,
464            imports,
465            decls,
466            span: crate::ast::Span::new(0, self.src_len),
467        }
468    }
469
470    fn skip_balanced_parens(&mut self) {
471        let mut depth = 0usize;
472        while let Some(t) = self.peek() {
473            match t {
474                TokenKind::LParen => depth += 1,
475                TokenKind::RParen => {
476                    if depth == 0 {
477                        return;
478                    }
479                    depth -= 1;
480                    if depth == 0 {
481                        self.i += 1;
482                        return;
483                    }
484                }
485                _ => {}
486            }
487            self.i += 1;
488        }
489    }
490
491    /// If the cursor sits on an infix operator equation with a pattern
492    /// left operand (`[] !! _ = ...`, `None <?> s = ...`), skip it and
493    /// return true. Operators have no IR surface.
494    fn try_infix_operator_decl(&mut self) -> bool {
495        let snap = self.i;
496        let saved_diags = self.diags.len();
497        if self.pattern().is_some()
498            && matches!(self.peek(), Some(TokenKind::Op(o)) if !is_reserved_op(o))
499        {
500            self.skip_to_item_end();
501            return true;
502        }
503        self.i = snap;
504        self.diags.truncate(saved_diags);
505        false
506    }
507
508    fn declaration(&mut self, imports: &mut Vec<ImportDecl>, decls: &mut Vec<Decl>) {
509        let pos = self.pos();
510        let start = self.i;
511        if matches!(
512            self.peek(),
513            Some(TokenKind::UpperId { .. } | TokenKind::LBracket | TokenKind::LParen)
514        ) && self.try_infix_operator_decl()
515        {
516            decls.push(Decl::Unknown {
517                raw: self.slice_text(start),
518                pos,
519                span: self.node_span(start),
520            });
521            return;
522        }
523        match self.peek() {
524            Some(t) if t.is_keyword("import") => {
525                let imp = self.import_decl();
526                // The `(...)` import list / `hiding (...)` clause is consumed
527                // here, after the decl is built; fold it into the span so the
528                // import covers its whole source extent.
529                self.skip_to_item_end();
530                if let Some(mut imp) = imp {
531                    imp.span = self.node_span(start);
532                    imports.push(imp);
533                }
534            }
535            Some(t) if t.is_keyword("template") => {
536                // `template T = ...` (template-let synonym) is exotic; only
537                // `template Name with/where` is a template declaration.
538                match self.template_decl() {
539                    Some(t) => decls.push(Decl::Template(t)),
540                    None => {
541                        self.skip_to_item_end();
542                        decls.push(Decl::Unknown {
543                            raw: self.slice_text(start),
544                            span: self.node_span(start),
545                            pos,
546                        });
547                    }
548                }
549            }
550            Some(t) if t.is_keyword("interface") => match self.interface_decl() {
551                Some(i) => decls.push(Decl::Interface(i)),
552                None => {
553                    self.skip_to_item_end();
554                    decls.push(Decl::Unknown {
555                        raw: self.slice_text(start),
556                        span: self.node_span(start),
557                        pos,
558                    });
559                }
560            },
561            Some(t)
562                if matches!(
563                    t.keyword(),
564                    // Fixity declarations, class-default declarations, and
565                    // pattern synonyms have no IR surface.
566                    Some("infix" | "infixl" | "infixr" | "default" | "pattern")
567                ) =>
568            {
569                self.skip_to_item_end();
570                decls.push(Decl::Unknown {
571                    raw: self.slice_text(start),
572                    pos,
573                    span: self.node_span(start),
574                });
575            }
576            Some(t)
577                if matches!(
578                    t.keyword(),
579                    Some(
580                        "data"
581                            | "type"
582                            | "newtype"
583                            | "class"
584                            | "instance"
585                            | "exception"
586                            | "deriving"
587                    )
588                ) =>
589            {
590                let keyword = t.keyword().unwrap().to_string();
591                self.bump();
592                let name = match self.peek() {
593                    Some(TokenKind::UpperId { qualifier, name }) => {
594                        let n = qualifier
595                            .as_ref()
596                            .map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
597                        self.bump();
598                        n
599                    }
600                    _ => String::new(),
601                };
602                self.skip_to_item_end();
603                decls.push(Decl::TypeDef {
604                    keyword,
605                    name: name.into(),
606                    pos,
607                    span: self.node_span(start),
608                });
609            }
610            Some(TokenKind::LowerId { .. }) => match self.function_item() {
611                Some(d) => decls.push(d),
612                None => {
613                    self.skip_to_item_end();
614                    decls.push(Decl::Unknown {
615                        raw: self.slice_text(start),
616                        span: self.node_span(start),
617                        pos,
618                    });
619                }
620            },
621            // Operator definition or signature: `(<=) = curry Lte`.
622            Some(TokenKind::LParen)
623                if matches!(self.peek_at(1), Some(TokenKind::Op(_)))
624                    && self.peek_at(2) == Some(&TokenKind::RParen) =>
625            {
626                self.skip_to_item_end();
627                decls.push(Decl::Unknown {
628                    raw: self.slice_text(start),
629                    span: self.node_span(start),
630                    pos,
631                });
632            }
633            // Top-level pattern binding: `[a, b, c] = ...`, `(x, y) = ...`.
634            Some(TokenKind::LParen | TokenKind::LBracket) => {
635                if self.binding().is_none() {
636                    self.diag_cat(
637                        DiagnosticCategory::SkippedDecl,
638                        "unparseable top-level pattern binding",
639                    );
640                }
641                self.skip_to_item_end();
642                decls.push(Decl::Unknown {
643                    raw: self.slice_text(start),
644                    span: self.node_span(start),
645                    pos,
646                });
647            }
648            _ => {
649                self.diag_cat(
650                    DiagnosticCategory::SkippedDecl,
651                    format!("unrecognized declaration: {:?}", self.peek()),
652                );
653                self.skip_to_item_end();
654                decls.push(Decl::Unknown {
655                    raw: self.slice_text(start),
656                    span: self.node_span(start),
657                    pos,
658                });
659            }
660        }
661    }
662
663    // ----- imports -----------------------------------------------------
664
665    fn import_decl(&mut self) -> Option<ImportDecl> {
666        let pos = self.pos();
667        let start_i = self.i;
668        self.bump(); // import
669        let mut style = if self.eat_keyword("qualified") {
670            ImportStyle::Qualified
671        } else {
672            ImportStyle::Unqualified
673        };
674        // Package-qualified import: `import qualified "pkg-name" Main as V1`.
675        if matches!(self.peek(), Some(TokenKind::StringLit(_))) {
676            self.bump();
677        }
678        let module_name = match self.peek().cloned() {
679            Some(TokenKind::UpperId { qualifier, name }) => {
680                self.bump();
681                match qualifier {
682                    Some(q) => format!("{q}.{name}").into(),
683                    None => name.into(),
684                }
685            }
686            _ => {
687                self.diag("expected module name after 'import'");
688                return None;
689            }
690        };
691        // ImportQualifiedPost style: `import DA.Map qualified as Map`.
692        if self.eat_keyword("qualified") {
693            style = ImportStyle::Qualified;
694        }
695        let mut alias = None;
696        if self.eat_keyword("as") {
697            if let Some(TokenKind::UpperId { qualifier, name }) = self.peek().cloned() {
698                self.bump();
699                alias = Some(match qualifier {
700                    Some(q) => format!("{q}.{name}").into(),
701                    None => name.into(),
702                });
703            }
704        }
705        // `hiding (...)` / import list — consumed by skip_to_item_end.
706        Some(ImportDecl {
707            module_name,
708            style,
709            alias,
710            pos,
711            span: self.node_span(start_i),
712        })
713    }
714
715    // ----- templates ---------------------------------------------------
716
717    fn upper_name(&mut self) -> Option<ModuleName> {
718        match self.peek().cloned() {
719            Some(TokenKind::UpperId { qualifier, name }) => {
720                self.bump();
721                Some(match qualifier {
722                    Some(q) => format!("{q}.{name}").into(),
723                    None => name.into(),
724                })
725            }
726            _ => None,
727        }
728    }
729
730    fn template_decl(&mut self) -> Option<TemplateDecl> {
731        let pos = self.pos();
732        let start_i = self.i;
733        self.bump(); // template
734        if self.at_keyword("instance") {
735            return None; // legacy `template instance` — not a template
736        }
737        let name = self.upper_name()?.to_string().into();
738
739        let fields = self
740            .eat_keyword("with")
741            .then(|| self.field_block())
742            .map(|parsed| parsed.fields)
743            .unwrap_or_default();
744        let body = if self.eat_keyword("where") {
745            self.template_body()
746        } else {
747            Vec::new()
748        };
749        Some(TemplateDecl {
750            name,
751            fields,
752            body,
753            pos,
754            span: self.node_span(start_i),
755        })
756    }
757
758    /// `{ name : Type ; name2, name3 : Type ; ... }` (virtual or explicit).
759    /// Returns the parsed fields and a "dangling" marker when the block was
760    /// entered but abandoned early because its first item is not a field
761    /// (an empty `with` whose layout block swallowed the next clause). The
762    /// caller should discard the block's eventual closing `VRBrace` when this
763    /// happens.
764    fn field_block(&mut self) -> FieldBlock {
765        let mut fields = Vec::new();
766        if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
767            return FieldBlock {
768                fields,
769                dangling: false,
770            };
771        }
772        loop {
773            while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
774            match self.peek() {
775                None => break,
776                Some(TokenKind::VRBrace | TokenKind::RBrace) => {
777                    self.bump();
778                    break;
779                }
780                // A stray closing bracket inside a block is garbage from a
781                // failed item parse — discard it or the loop cannot make
782                // progress (skip_to_item_end deliberately stops before
783                // unmatched closers).
784                Some(TokenKind::RParen | TokenKind::RBracket) => {
785                    self.bump();
786                    continue;
787                }
788                _ => {}
789            }
790            // A field item must look like `name [, name] : Type`. If the
791            // next item doesn't (an empty `with` swallowed the following
792            // clause into its layout block — `with` + comment + `controller`),
793            // stop without consuming so the caller can parse the clause.
794            {
795                let mut j = self.i;
796                while let Some(TokenKind::LowerId {
797                    qualifier: None, ..
798                }) = self.toks.get(j).map(|t| &t.kind)
799                {
800                    j += 1;
801                    match self.toks.get(j).map(|t| &t.kind) {
802                        Some(TokenKind::Comma) => j += 1,
803                        _ => break,
804                    }
805                }
806                let is_field = j > self.i
807                    && self
808                        .toks
809                        .get(j)
810                        .map(|t| &t.kind)
811                        .is_some_and(|t| t.is_op(":"));
812                if !is_field {
813                    return FieldBlock {
814                        fields,
815                        dangling: true,
816                    };
817                }
818            }
819            // One or more comma-separated names, then `:`, then the type.
820            let mut names: Vec<(Identifier, Pos, Span)> = Vec::new();
821            while let Some(TokenKind::LowerId {
822                qualifier: None,
823                name,
824            }) = self.peek().cloned()
825            {
826                let p = self.pos();
827                let nspan = Span::new(self.toks[self.i].start, self.toks[self.i].end);
828                self.bump();
829                names.push((name, p, nspan));
830                if !self.eat(&TokenKind::Comma) {
831                    break;
832                }
833            }
834            if names.is_empty() || !self.eat_op(":") {
835                self.diag("expected 'name : Type' field");
836                self.skip_to_item_end();
837                continue;
838            }
839            let ty_start = self.i;
840            self.skip_to_item_end();
841            let ty = self.parse_type_annotation(ty_start, self.i, "field");
842            // The type is shared by all names but sits after the last one, so
843            // only the last field can span `name : Type` without overlapping a
844            // sibling; earlier names of `x, y : T` stay name-only. daml-fmt
845            // reads the type extent off the last field of a comma group.
846            let type_end = self.end_byte();
847            let last = names.len() - 1;
848            for (idx, (name, p, nspan)) in names.into_iter().enumerate() {
849                let span = if idx == last {
850                    Span::new(nspan.start, type_end.max(nspan.end))
851                } else {
852                    nspan
853                };
854                fields.push(FieldDecl {
855                    name: name.to_string().into(),
856                    ty: ty.clone(),
857                    pos: p,
858                    span,
859                });
860            }
861        }
862        FieldBlock {
863            fields,
864            dangling: false,
865        }
866    }
867
868    // ----- template body ------------------------------------------------
869
870    fn template_body(&mut self) -> Vec<TemplateBodyDecl> {
871        let mut body = Vec::new();
872        if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
873            return body;
874        }
875        loop {
876            while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
877            match self.peek() {
878                None => break,
879                Some(TokenKind::VRBrace | TokenKind::RBrace) => {
880                    self.bump();
881                    break;
882                }
883                // A stray closing bracket inside a block is garbage from a
884                // failed item parse — discard it or the loop cannot make
885                // progress (skip_to_item_end deliberately stops before
886                // unmatched closers).
887                Some(TokenKind::RParen | TokenKind::RBracket) => {
888                    self.bump();
889                    continue;
890                }
891                _ => {}
892            }
893            let pos = self.pos();
894            let start = self.i;
895            let decl = self.template_body_item(pos, start);
896            body.push(decl);
897        }
898        body
899    }
900
901    fn template_body_item(&mut self, pos: Pos, start: usize) -> TemplateBodyDecl {
902        match self.peek().and_then(|t| t.keyword()) {
903            Some("signatory") => {
904                self.bump();
905                let parties = self.expr_comma_list();
906                self.skip_to_item_end();
907                TemplateBodyDecl::Signatory {
908                    parties,
909                    pos,
910                    span: self.node_span(start),
911                }
912            }
913            Some("observer") => {
914                self.bump();
915                let parties = self.expr_comma_list();
916                self.skip_to_item_end();
917                TemplateBodyDecl::Observer {
918                    parties,
919                    pos,
920                    span: self.node_span(start),
921                }
922            }
923            Some("ensure") => {
924                self.bump();
925                let expr = self.expr();
926                self.skip_to_item_end();
927                TemplateBodyDecl::Ensure {
928                    expr,
929                    pos,
930                    span: self.node_span(start),
931                }
932            }
933            Some("key") => {
934                self.bump();
935                let expr_start = self.i;
936                let expr = self.expr();
937                let ty = if self.eat_op(":") {
938                    let ty_start = self.i;
939                    self.skip_to_item_end();
940                    self.parse_type_annotation(ty_start, self.i, "key")
941                } else {
942                    // The expression parser consumes `: Type` annotations;
943                    // recover the key type from the last top-level colon.
944                    let mut depth = 0i32;
945                    let mut colon = None;
946                    for j in expr_start..self.i {
947                        match &self.toks[j].kind {
948                            TokenKind::LParen | TokenKind::LBracket => depth += 1,
949                            TokenKind::RParen | TokenKind::RBracket if depth > 0 => depth -= 1,
950                            TokenKind::Op(o) if o.as_str() == ":" && depth == 0 => colon = Some(j),
951                            _ => {}
952                        }
953                    }
954                    let ty = colon.and_then(|j| self.parse_type_annotation(j + 1, self.i, "key"));
955                    self.skip_to_item_end();
956                    ty
957                };
958                TemplateBodyDecl::Key {
959                    expr,
960                    ty,
961                    pos,
962                    span: self.node_span(start),
963                }
964            }
965            Some("maintainer") => {
966                self.bump();
967                let expr = self.expr();
968                self.skip_to_item_end();
969                TemplateBodyDecl::Maintainer {
970                    expr,
971                    pos,
972                    span: self.node_span(start),
973                }
974            }
975            Some("choice" | "nonconsuming" | "preconsuming" | "postconsuming") => {
976                self.choice_decl().map_or_else(
977                    || {
978                        self.skip_to_item_end();
979                        TemplateBodyDecl::Other {
980                            raw: self.slice_text(start),
981                            span: self.node_span(start),
982                            pos,
983                        }
984                    },
985                    TemplateBodyDecl::Choice,
986                )
987            }
988            Some("interface") => self.interface_instance_decl().map_or_else(
989                || {
990                    self.skip_to_item_end();
991                    TemplateBodyDecl::Other {
992                        raw: self.slice_text(start),
993                        span: self.node_span(start),
994                        pos,
995                    }
996                },
997                TemplateBodyDecl::InterfaceInstance,
998            ),
999            Some("controller") => {
1000                // Legacy Daml 1.x `controller <party> can` choice blocks are
1001                // not analyzed — fail loud instead of silently dropping the
1002                // choices inside.
1003                self.diag_cat(
1004                    DiagnosticCategory::UnsupportedSyntax,
1005                    "legacy 'controller ... can' syntax is not supported; \
1006                     choices inside this block are not analyzed",
1007                );
1008                self.skip_to_item_end();
1009                TemplateBodyDecl::Other {
1010                    raw: self.slice_text(start),
1011                    span: self.node_span(start),
1012                    pos,
1013                }
1014            }
1015            _ => {
1016                self.skip_to_item_end();
1017                TemplateBodyDecl::Other {
1018                    raw: self.slice_text(start),
1019                    span: self.node_span(start),
1020                    pos,
1021                }
1022            }
1023        }
1024    }
1025
1026    fn choice_decl(&mut self) -> Option<ChoiceDecl> {
1027        let pos = self.pos();
1028        let start_i = self.i;
1029        let consuming = match self.peek().and_then(|t| t.keyword()) {
1030            Some("nonconsuming") => {
1031                self.bump();
1032                Consuming::NonConsuming
1033            }
1034            Some("preconsuming") => {
1035                self.bump();
1036                Consuming::PreConsuming
1037            }
1038            Some("postconsuming") => {
1039                self.bump();
1040                Consuming::PostConsuming
1041            }
1042            _ => Consuming::Consuming,
1043        };
1044        if !self.eat_keyword("choice") {
1045            return None;
1046        }
1047        let name = self.upper_name()?.to_string().into();
1048        let return_ty = if self.eat_op(":") {
1049            let ty_start = self.i;
1050            self.skip_type_tokens();
1051            self.parse_type_annotation(ty_start, self.i, "choice")
1052        } else {
1053            None
1054        };
1055        let (params, dangling) = if self.eat_keyword("with") {
1056            let parsed = self.field_block();
1057            (parsed.fields, parsed.dangling)
1058        } else {
1059            (Vec::new(), false)
1060        };
1061        let mut observers = Vec::new();
1062        let mut controllers = Vec::new();
1063        loop {
1064            // Inside a dangling (empty) with-block the controller/observer/
1065            // do clauses sit at the block's column, so layout separates
1066            // them with virtual semicolons — consume those.
1067            if dangling {
1068                while self.eat(&TokenKind::VSemi) {}
1069            }
1070            if self.eat_keyword("observer") {
1071                observers = self.expr_comma_list_no_do();
1072            } else if self.eat_keyword("controller") {
1073                controllers = self.expr_comma_list_no_do();
1074            } else {
1075                break;
1076            }
1077        }
1078        if dangling {
1079            while self.eat(&TokenKind::VSemi) {}
1080        }
1081        let body = if self.peek().is_some_and(|t| {
1082            !matches!(
1083                t,
1084                TokenKind::VSemi | TokenKind::VRBrace | TokenKind::Semi | TokenKind::RBrace
1085            )
1086        }) {
1087            Some(self.expr())
1088        } else {
1089            None
1090        };
1091        self.skip_to_item_end();
1092        if dangling {
1093            // Discard the abandoned with-block's closing brace so it does
1094            // not terminate the enclosing template/interface body.
1095            self.eat(&TokenKind::VRBrace);
1096            self.skip_to_item_end();
1097        }
1098        Some(ChoiceDecl {
1099            name,
1100            consuming,
1101            return_ty,
1102            params,
1103            controllers,
1104            observers,
1105            body,
1106            pos,
1107            span: self.node_span(start_i),
1108        })
1109    }
1110
1111    /// Consume type tokens up to (not including) a layout boundary or a
1112    /// `with`/`controller`/`observer`/`do`/`where` keyword at bracket depth 0.
1113    fn skip_type_tokens(&mut self) {
1114        let mut brackets = 0usize;
1115        while let Some(t) = self.peek() {
1116            match t {
1117                TokenKind::VSemi | TokenKind::VRBrace | TokenKind::VLBrace | TokenKind::Semi => {
1118                    return
1119                }
1120                TokenKind::LParen | TokenKind::LBracket => brackets += 1,
1121                TokenKind::RParen | TokenKind::RBracket => {
1122                    if brackets == 0 {
1123                        return;
1124                    }
1125                    brackets -= 1;
1126                }
1127                _ if brackets == 0
1128                    && matches!(
1129                        t.keyword(),
1130                        Some("with" | "controller" | "observer" | "do" | "where")
1131                    ) =>
1132                {
1133                    return
1134                }
1135                _ => {}
1136            }
1137            self.i += 1;
1138        }
1139    }
1140
1141    // ----- interfaces ----------------------------------------------------
1142
1143    fn interface_decl(&mut self) -> Option<InterfaceDecl> {
1144        let pos = self.pos();
1145        let start_i = self.i;
1146        self.bump(); // interface
1147        if self.at_keyword("instance") {
1148            // Top-level retroactive interface instance: skip gracefully.
1149            return None;
1150        }
1151        let name = self.upper_name()?.to_string().into();
1152        let mut requires = Vec::new();
1153        if self.eat_keyword("requires") {
1154            while let Some(r) = self.upper_name() {
1155                requires.push(r);
1156                if !self.eat(&TokenKind::Comma) {
1157                    break;
1158                }
1159            }
1160        }
1161        if !self.eat_keyword("where") {
1162            return None;
1163        }
1164        let mut viewtype = None;
1165        let mut methods = Vec::new();
1166        let mut choices = Vec::new();
1167        if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
1168            return Some(InterfaceDecl {
1169                name,
1170                requires,
1171                viewtype,
1172                methods,
1173                choices,
1174                pos,
1175                span: self.node_span(start_i),
1176            });
1177        }
1178        loop {
1179            while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
1180            match self.peek() {
1181                None => break,
1182                Some(TokenKind::VRBrace | TokenKind::RBrace) => {
1183                    self.bump();
1184                    break;
1185                }
1186                // A stray closing bracket inside a block is garbage from a
1187                // failed item parse — discard it or the loop cannot make
1188                // progress (skip_to_item_end deliberately stops before
1189                // unmatched closers).
1190                Some(TokenKind::RParen | TokenKind::RBracket) => {
1191                    self.bump();
1192                    continue;
1193                }
1194                _ => {}
1195            }
1196            match self.peek().and_then(|t| t.keyword()) {
1197                Some("viewtype") => {
1198                    self.bump();
1199                    viewtype = self.upper_name();
1200                    self.skip_to_item_end();
1201                }
1202                Some("choice" | "nonconsuming" | "preconsuming" | "postconsuming") => {
1203                    if let Some(c) = self.choice_decl() {
1204                        choices.push(c);
1205                    } else {
1206                        self.skip_to_item_end();
1207                    }
1208                }
1209                _ => {
1210                    // Method signature `name : Type`; anything else (default
1211                    // implementations, ensure, ...) is skipped.
1212                    let mpos = self.pos();
1213                    if let Some(TokenKind::LowerId {
1214                        qualifier: None,
1215                        name: mname,
1216                    }) = self.peek().cloned()
1217                    {
1218                        if self.peek_at(1).is_some_and(|t| t.is_op(":")) {
1219                            let mstart = self.toks[self.i].start;
1220                            self.bump();
1221                            self.bump();
1222                            let ty_start = self.i;
1223                            self.skip_to_item_end();
1224                            // Single name: span the whole `name : Type`.
1225                            methods.push(FieldDecl {
1226                                name: mname,
1227                                ty: self.parse_type_annotation(
1228                                    ty_start,
1229                                    self.i,
1230                                    "interface method",
1231                                ),
1232                                pos: mpos,
1233                                span: Span::new(mstart, self.end_byte().max(mstart)),
1234                            });
1235                            continue;
1236                        }
1237                    }
1238                    self.skip_to_item_end();
1239                }
1240            }
1241        }
1242        Some(InterfaceDecl {
1243            name,
1244            requires,
1245            viewtype,
1246            methods,
1247            choices,
1248            pos,
1249            span: self.node_span(start_i),
1250        })
1251    }
1252
1253    /// `interface instance I for T where { method-bindings }`
1254    fn interface_instance_decl(&mut self) -> Option<InterfaceInstanceDecl> {
1255        let pos = self.pos();
1256        let start_i = self.i;
1257        self.bump(); // interface
1258        if !self.eat_keyword("instance") {
1259            return None;
1260        }
1261        let interface_name = self.upper_name()?;
1262        let for_template = if self.eat_keyword("for") {
1263            self.upper_name().unwrap_or_default()
1264        } else {
1265            ModuleName::default()
1266        };
1267        let mut methods = Vec::new();
1268        if self.eat_keyword("where")
1269            && (self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace))
1270        {
1271            loop {
1272                while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
1273                match self.peek() {
1274                    None => break,
1275                    Some(TokenKind::VRBrace | TokenKind::RBrace) => {
1276                        self.bump();
1277                        break;
1278                    }
1279                    // Stray closer: discard so the loop always progresses.
1280                    Some(TokenKind::RParen | TokenKind::RBracket) => {
1281                        self.bump();
1282                        continue;
1283                    }
1284                    _ => {}
1285                }
1286                if let Some(b) = self.binding() {
1287                    methods.push(b);
1288                } else {
1289                    self.skip_to_item_end();
1290                }
1291            }
1292        }
1293        Some(InterfaceInstanceDecl {
1294            interface_name,
1295            for_template,
1296            methods,
1297            pos,
1298            span: self.node_span(start_i),
1299        })
1300    }
1301
1302    // ----- functions -----------------------------------------------------
1303
1304    /// A top-level item starting with a lowercase identifier: type
1305    /// signature or function equation. Operator definitions and other
1306    /// exotica return None.
1307    fn function_item(&mut self) -> Option<Decl> {
1308        let pos = self.pos();
1309        let start_i = self.i;
1310        let name = match self.peek().cloned() {
1311            Some(TokenKind::LowerId {
1312                qualifier: None,
1313                name,
1314            }) => name,
1315            _ => return None,
1316        };
1317
1318        // Type signature: `name [, name2] : Type`
1319        let mut j = self.i + 1;
1320        let mut is_sig = false;
1321        loop {
1322            match self.toks.get(j).map(|t| &t.kind) {
1323                Some(TokenKind::Comma) => {
1324                    j += 1;
1325                    if matches!(
1326                        self.toks.get(j).map(|t| &t.kind),
1327                        Some(TokenKind::LowerId {
1328                            qualifier: None,
1329                            ..
1330                        })
1331                    ) {
1332                        j += 1;
1333                        continue;
1334                    }
1335                    break;
1336                }
1337                Some(TokenKind::Op(o)) if o.as_str() == ":" => {
1338                    is_sig = true;
1339                    break;
1340                }
1341                _ => break,
1342            }
1343        }
1344        if is_sig {
1345            self.bump(); // name
1346            while self.eat(&TokenKind::Comma) {
1347                self.bump(); // more names
1348            }
1349            self.eat_op(":");
1350            let ty_start = self.i;
1351            self.skip_to_item_end();
1352            let ty = self.parse_type_annotation(ty_start, self.i, "function");
1353            return Some(Decl::Function(FunctionDecl {
1354                name,
1355                ty,
1356                equations: Vec::new(),
1357                pos,
1358                sig_span: Some(self.node_span(start_i)),
1359                span: self.node_span(start_i),
1360            }));
1361        }
1362
1363        // Function equation: name pats (= expr | guards), optional where.
1364        self.bump(); // name
1365        let mut params = Vec::new();
1366        while !self.at_op("=") && !self.at_op("|") {
1367            // Combined signature + body: `name (x : a) : RetType = expr` —
1368            // consume the return-type annotation up to the `=`.
1369            if self.at_op(":") {
1370                self.bump();
1371                let mut brackets = 0usize;
1372                while let Some(t) = self.peek() {
1373                    match t {
1374                        TokenKind::Op(o) if o.as_str() == "=" && brackets == 0 => break,
1375                        TokenKind::VSemi
1376                        | TokenKind::VRBrace
1377                        | TokenKind::Semi
1378                        | TokenKind::RBrace => break,
1379                        TokenKind::LParen | TokenKind::LBracket => brackets += 1,
1380                        TokenKind::RParen | TokenKind::RBracket => {
1381                            brackets = brackets.saturating_sub(1)
1382                        }
1383                        _ => {}
1384                    }
1385                    self.i += 1;
1386                }
1387                continue;
1388            }
1389            // Infix operator definition: `f $ x = f x`, `as <&> f = ...` —
1390            // operators have no IR surface; skip the item silently.
1391            if matches!(self.peek(), Some(TokenKind::Op(o)) if !is_reserved_op(o)) {
1392                self.skip_to_item_end();
1393                return None;
1394            }
1395            match self.peek() {
1396                None
1397                | Some(
1398                    TokenKind::VSemi | TokenKind::VRBrace | TokenKind::Semi | TokenKind::RBrace,
1399                ) => {
1400                    self.diag(format!("could not parse equation for '{name}'"));
1401                    return None;
1402                }
1403                _ => {}
1404            }
1405            match self.pattern_atom() {
1406                Some(p) => params.push(p),
1407                None => {
1408                    self.diag(format!("bad parameter pattern in '{name}'"));
1409                    return None;
1410                }
1411            }
1412        }
1413        let (body, guards) = self.equation_rhs()?;
1414        let where_bindings = if self.eat_keyword("where") {
1415            self.binding_block()
1416        } else {
1417            Vec::new()
1418        };
1419        self.skip_to_item_end();
1420        Some(Decl::Function(FunctionDecl {
1421            name,
1422            ty: None,
1423            equations: vec![Equation {
1424                params,
1425                body,
1426                guards,
1427                where_bindings,
1428                pos,
1429                span: self.node_span(start_i),
1430            }],
1431            pos,
1432            sig_span: None,
1433            span: self.node_span(start_i),
1434        }))
1435    }
1436
1437    /// `= expr` or `| guard = expr | guard = expr ...`
1438    fn equation_rhs(&mut self) -> Option<(Expr, Vec<(Expr, Expr)>)> {
1439        if self.eat_op("=") {
1440            return Some((self.expr(), Vec::new()));
1441        }
1442        let mut guards = Vec::new();
1443        while self.eat_op("|") {
1444            // Comma-separated guard qualifiers, each a boolean expression
1445            // or a pattern guard `pat <- expr`.
1446            let g = loop {
1447                let g = self.expr();
1448                if self.eat_op("<-") {
1449                    let _ = self.expr(); // pattern guard: keep the pattern side
1450                }
1451                if !self.eat(&TokenKind::Comma) {
1452                    break g;
1453                }
1454            };
1455            if !self.eat_op("=") {
1456                self.diag("expected '=' after guard");
1457                return None;
1458            }
1459            let e = self.expr();
1460            guards.push((g, e));
1461        }
1462        if guards.is_empty() {
1463            self.diag("expected '=' or guarded right-hand side in equation");
1464            None
1465        } else {
1466            let first = guards[0].1.clone();
1467            Some((first, guards))
1468        }
1469    }
1470
1471    /// `{ binding ; binding ; ... }` for let/where blocks.
1472    fn binding_block(&mut self) -> Vec<Binding> {
1473        let mut bindings = Vec::new();
1474        if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
1475            return bindings;
1476        }
1477        loop {
1478            while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
1479            match self.peek() {
1480                None => break,
1481                Some(TokenKind::VRBrace | TokenKind::RBrace) => {
1482                    self.bump();
1483                    break;
1484                }
1485                // A stray closing bracket inside a block is garbage from a
1486                // failed item parse — discard it or the loop cannot make
1487                // progress (skip_to_item_end deliberately stops before
1488                // unmatched closers).
1489                Some(TokenKind::RParen | TokenKind::RBracket) => {
1490                    self.bump();
1491                    continue;
1492                }
1493                _ => {}
1494            }
1495            match self.binding() {
1496                Some(b) => bindings.push(b),
1497                None => self.skip_to_item_end(),
1498            }
1499        }
1500        bindings
1501    }
1502
1503    /// One binding: `pat = expr`, `f x y = expr`, guarded variants, or a
1504    /// type signature (skipped, returns None).
1505    fn binding(&mut self) -> Option<Binding> {
1506        let pos = self.pos();
1507        let start_i = self.i;
1508        // Operator binding or signature: `(==) : Text -> Bool = ...` —
1509        // skip the whole item; operators aren't surfaced in the IR.
1510        if self.at(&TokenKind::LParen)
1511            && matches!(self.peek_at(1), Some(TokenKind::Op(_)))
1512            && self.peek_at(2) == Some(&TokenKind::RParen)
1513        {
1514            self.skip_to_item_end();
1515            return None;
1516        }
1517        let pat = self.pattern_atom()?;
1518        let mut params = Vec::new();
1519        loop {
1520            if self.at_op("=") {
1521                self.bump();
1522                let expr = self.expr();
1523                // Bindings can carry their own where blocks.
1524                if self.eat_keyword("where") {
1525                    let _ = self.binding_block();
1526                }
1527                return Some(Binding {
1528                    pat,
1529                    params,
1530                    expr,
1531                    pos,
1532                    span: self.node_span(start_i),
1533                });
1534            }
1535            if self.at_op("|") {
1536                let (body, _) = self.equation_rhs()?;
1537                if self.eat_keyword("where") {
1538                    let _ = self.binding_block();
1539                }
1540                return Some(Binding {
1541                    pat,
1542                    params,
1543                    expr: body,
1544                    pos,
1545                    span: self.node_span(start_i),
1546                });
1547            }
1548            if self.at_op(":") {
1549                if params.is_empty() {
1550                    // Type signature inside a let/where block — skip it.
1551                    self.skip_to_item_end();
1552                    return None;
1553                }
1554                // Combined signature + body: `f (x : a) : Ret = expr` —
1555                // consume the return-type annotation up to the `=`.
1556                self.bump();
1557                let mut brackets = 0usize;
1558                while let Some(t) = self.peek() {
1559                    match t {
1560                        TokenKind::Op(o) if o.as_str() == "=" && brackets == 0 => break,
1561                        TokenKind::VSemi
1562                        | TokenKind::VRBrace
1563                        | TokenKind::Semi
1564                        | TokenKind::RBrace => break,
1565                        TokenKind::LParen | TokenKind::LBracket => brackets += 1,
1566                        TokenKind::RParen | TokenKind::RBracket => {
1567                            brackets = brackets.saturating_sub(1)
1568                        }
1569                        _ => {}
1570                    }
1571                    self.i += 1;
1572                }
1573                continue;
1574            }
1575            // Infix operator binding with a pattern operand:
1576            // `None <?> s = ...` in a where/let block.
1577            if matches!(self.peek(), Some(TokenKind::Op(o)) if !is_reserved_op(o)) {
1578                self.skip_to_item_end();
1579                return None;
1580            }
1581            match self.peek() {
1582                None
1583                | Some(
1584                    TokenKind::VSemi | TokenKind::VRBrace | TokenKind::Semi | TokenKind::RBrace,
1585                ) => return None,
1586                _ => {}
1587            }
1588            params.push(self.pattern_atom()?);
1589        }
1590    }
1591
1592    // ----- patterns ------------------------------------------------------
1593
1594    fn pattern_atom(&mut self) -> Option<Pat> {
1595        if self.depth >= MAX_RECURSION_DEPTH {
1596            return None;
1597        }
1598        self.depth += 1;
1599        let result = self.pattern_atom_inner();
1600        self.depth -= 1;
1601        result
1602    }
1603
1604    fn pattern_atom_inner(&mut self) -> Option<Pat> {
1605        let pos = self.pos();
1606        let start_i = self.i;
1607        // Lazy / strict pattern markers: `~(as, bs)`, `!x`.
1608        if self.at_op("~") || self.at_op("!") {
1609            self.bump();
1610            return self.pattern_atom();
1611        }
1612        match self.peek().cloned() {
1613            Some(TokenKind::LowerId {
1614                qualifier: None,
1615                name,
1616            }) => {
1617                self.bump();
1618                if name == "_" {
1619                    return Some(Pat::Wild {
1620                        pos,
1621                        span: self.node_span(start_i),
1622                    });
1623                }
1624                if self.at_op("@") {
1625                    self.bump();
1626                    let inner = self.pattern_atom()?;
1627                    return Some(Pat::As {
1628                        name,
1629                        pat: Box::new(inner),
1630                        pos,
1631                        span: self.node_span(start_i),
1632                    });
1633                }
1634                Some(Pat::Var {
1635                    name,
1636                    pos,
1637                    span: self.node_span(start_i),
1638                })
1639            }
1640            Some(TokenKind::Op(o)) if o.as_str() == "_" => {
1641                self.bump();
1642                Some(Pat::Wild {
1643                    pos,
1644                    span: self.node_span(start_i),
1645                })
1646            }
1647            Some(TokenKind::UpperId { qualifier, name }) => {
1648                self.bump();
1649                // Record pattern `Foo {..}` / `Foo {x = y}` /
1650                // `Foo with claim; tag`.
1651                if self.at(&TokenKind::LBrace) {
1652                    self.skip_balanced_braces();
1653                } else if self.eat_keyword("with") {
1654                    let _ = self.record_fields();
1655                }
1656                Some(Pat::Con {
1657                    qualifier,
1658                    name,
1659                    args: Vec::new(),
1660                    pos,
1661                    span: self.node_span(start_i),
1662                })
1663            }
1664            Some(TokenKind::IntLit(text)) => {
1665                self.bump();
1666                Some(Pat::Lit {
1667                    kind: LitKind::Int,
1668                    text,
1669                    pos,
1670                    span: self.node_span(start_i),
1671                })
1672            }
1673            Some(TokenKind::DecimalLit(text)) => {
1674                self.bump();
1675                Some(Pat::Lit {
1676                    kind: LitKind::Decimal,
1677                    text,
1678                    pos,
1679                    span: self.node_span(start_i),
1680                })
1681            }
1682            Some(TokenKind::StringLit(text)) => {
1683                self.bump();
1684                Some(Pat::Lit {
1685                    kind: LitKind::Text,
1686                    text,
1687                    pos,
1688                    span: self.node_span(start_i),
1689                })
1690            }
1691            Some(TokenKind::CharLit(text)) => {
1692                self.bump();
1693                Some(Pat::Lit {
1694                    kind: LitKind::Char,
1695                    text,
1696                    pos,
1697                    span: self.node_span(start_i),
1698                })
1699            }
1700            Some(TokenKind::LParen) => {
1701                self.bump();
1702                if self.eat(&TokenKind::RParen) {
1703                    return Some(Pat::Con {
1704                        qualifier: None,
1705                        name: "()".into(),
1706                        args: Vec::new(),
1707                        pos,
1708                        span: self.node_span(start_i),
1709                    });
1710                }
1711                // View pattern `(expr -> pat)`: scan for a top-level `->`
1712                // inside these parens; the expression side is discarded and
1713                // the pattern after the arrow is the binding. A top-level
1714                // `:` before the arrow means the arrow belongs to a type
1715                // annotation (`(f : Int -> Bool)`), not a view pattern.
1716                {
1717                    let mut depth = 0usize;
1718                    let mut j = self.i;
1719                    let mut arrow = None;
1720                    while let Some(t) = self.toks.get(j).map(|t| &t.kind) {
1721                        match t {
1722                            TokenKind::LParen | TokenKind::LBracket => depth += 1,
1723                            TokenKind::RParen | TokenKind::RBracket => {
1724                                if depth == 0 {
1725                                    break;
1726                                }
1727                                depth -= 1;
1728                            }
1729                            TokenKind::Op(o) if o.as_str() == ":" && depth == 0 => break,
1730                            TokenKind::Op(o) if o.as_str() == "->" && depth == 0 => {
1731                                arrow = Some(j);
1732                                break;
1733                            }
1734                            TokenKind::VSemi | TokenKind::VRBrace => break,
1735                            // A lambda's arrow belongs to the lambda.
1736                            TokenKind::Op(o) if o.as_str() == "\\" => break,
1737                            _ => {}
1738                        }
1739                        j += 1;
1740                    }
1741                    if let Some(j) = arrow {
1742                        self.i = j + 1; // skip the view expression and `->`
1743                        let inner = self.pattern()?;
1744                        self.eat(&TokenKind::RParen);
1745                        return Some(inner);
1746                    }
1747                }
1748                let first = self.pattern()?;
1749                // Type-annotated pattern `(e : AnyException)`: skip the type.
1750                if self.at_op(":") {
1751                    let mut depth = 0usize;
1752                    while let Some(t) = self.peek() {
1753                        match t {
1754                            TokenKind::LParen | TokenKind::LBracket => depth += 1,
1755                            TokenKind::RParen if depth == 0 => break,
1756                            TokenKind::RParen | TokenKind::RBracket => {
1757                                depth = depth.saturating_sub(1)
1758                            }
1759                            TokenKind::VSemi | TokenKind::VRBrace => break,
1760                            _ => {}
1761                        }
1762                        self.i += 1;
1763                    }
1764                }
1765                if self.at(&TokenKind::Comma) {
1766                    let mut items = vec![first];
1767                    while self.eat(&TokenKind::Comma) {
1768                        items.push(self.pattern()?);
1769                    }
1770                    self.eat(&TokenKind::RParen);
1771                    return Some(Pat::Tuple {
1772                        items,
1773                        pos,
1774                        span: self.node_span(start_i),
1775                    });
1776                }
1777                self.eat(&TokenKind::RParen);
1778                Some(first)
1779            }
1780            Some(TokenKind::LBracket) => {
1781                self.bump();
1782                let mut items = Vec::new();
1783                if !self.eat(&TokenKind::RBracket) {
1784                    loop {
1785                        items.push(self.pattern()?);
1786                        if !self.eat(&TokenKind::Comma) {
1787                            break;
1788                        }
1789                    }
1790                    self.eat(&TokenKind::RBracket);
1791                }
1792                Some(Pat::List {
1793                    items,
1794                    pos,
1795                    span: self.node_span(start_i),
1796                })
1797            }
1798            _ => None,
1799        }
1800    }
1801
1802    /// Full pattern: constructor applications and infix cons `x :: xs`.
1803    fn pattern(&mut self) -> Option<Pat> {
1804        if self.depth >= MAX_RECURSION_DEPTH {
1805            return None;
1806        }
1807        self.depth += 1;
1808        let result = self.pattern_inner();
1809        self.depth -= 1;
1810        result
1811    }
1812
1813    fn pattern_inner(&mut self) -> Option<Pat> {
1814        let pos = self.pos();
1815        let start_i = self.i;
1816        let first = match self.peek().cloned() {
1817            Some(TokenKind::UpperId { qualifier, name }) => {
1818                self.bump();
1819                if self.at(&TokenKind::LBrace) || self.at_keyword("with") {
1820                    if self.eat_keyword("with") {
1821                        let _ = self.record_fields();
1822                    } else {
1823                        self.skip_balanced_braces();
1824                    }
1825                    Pat::Con {
1826                        qualifier,
1827                        name,
1828                        args: Vec::new(),
1829                        pos,
1830                        span: self.node_span(start_i),
1831                    }
1832                } else {
1833                    let mut args = Vec::new();
1834                    while let Some(a) = self.try_pattern_atom() {
1835                        args.push(a);
1836                    }
1837                    Pat::Con {
1838                        qualifier,
1839                        name,
1840                        args,
1841                        pos,
1842                        span: self.node_span(start_i),
1843                    }
1844                }
1845            }
1846            _ => self.pattern_atom()?,
1847        };
1848        if self.at_op("::") {
1849            self.bump();
1850            let rest = self.pattern()?;
1851            return Some(Pat::Con {
1852                qualifier: None,
1853                name: "::".into(),
1854                args: vec![first, rest],
1855                pos,
1856                span: self.node_span(start_i),
1857            });
1858        }
1859        Some(first)
1860    }
1861
1862    fn try_pattern_atom(&mut self) -> Option<Pat> {
1863        match self.peek() {
1864            Some(
1865                TokenKind::LowerId {
1866                    qualifier: None, ..
1867                }
1868                | TokenKind::UpperId { .. }
1869                | TokenKind::IntLit(_)
1870                | TokenKind::DecimalLit(_)
1871                | TokenKind::StringLit(_)
1872                | TokenKind::CharLit(_)
1873                | TokenKind::LParen
1874                | TokenKind::LBracket,
1875            ) => self.pattern_atom(),
1876            _ => None,
1877        }
1878    }
1879
1880    fn skip_balanced_braces(&mut self) {
1881        let mut depth = 0usize;
1882        while let Some(t) = self.peek() {
1883            match t {
1884                TokenKind::LBrace => depth += 1,
1885                TokenKind::RBrace => {
1886                    if depth == 0 {
1887                        return;
1888                    }
1889                    depth -= 1;
1890                    if depth == 0 {
1891                        self.i += 1;
1892                        return;
1893                    }
1894                }
1895                _ => {}
1896            }
1897            self.i += 1;
1898        }
1899    }
1900
1901    // ----- expressions ---------------------------------------------------
1902
1903    fn expr(&mut self) -> Expr {
1904        self.expr_prec(0, DoExpressionMode::Allow)
1905    }
1906
1907    fn expr_no_do(&mut self) -> Expr {
1908        self.expr_prec(0, DoExpressionMode::Disallow)
1909    }
1910
1911    /// Comma-separated expressions (signatory/observer/controller lists).
1912    fn expr_comma_list(&mut self) -> Vec<Expr> {
1913        let mut out = vec![self.expr()];
1914        while self.eat(&TokenKind::Comma) {
1915            out.push(self.expr());
1916        }
1917        out
1918    }
1919
1920    fn expr_comma_list_no_do(&mut self) -> Vec<Expr> {
1921        let mut out = vec![self.expr_no_do()];
1922        while self.eat(&TokenKind::Comma) {
1923            out.push(self.expr_no_do());
1924        }
1925        out
1926    }
1927
1928    fn expr_prec(&mut self, min_prec: u8, do_mode: DoExpressionMode) -> Expr {
1929        let pos = self.pos();
1930        let start_i = self.i;
1931        if self.depth >= MAX_RECURSION_DEPTH {
1932            // Hostile nesting: degrade to raw text instead of recursing, and
1933            // report it so the degraded region is not silently mistaken for
1934            // unsupported syntax. `skip_to_item_end` below consumes the rest of
1935            // the item, so this trips about once per affected declaration.
1936            self.diag_cat(
1937                DiagnosticCategory::RecursionLimit,
1938                "expression nesting too deep; truncated to raw text",
1939            );
1940            let start = self.i;
1941            self.skip_to_item_end();
1942            if self.i == start {
1943                self.bump();
1944            }
1945            return Expr::Error {
1946                raw: self.slice_text(start),
1947                span: self.node_span(start),
1948                pos,
1949            };
1950        }
1951        self.depth += 1;
1952        let result = self.expr_prec_inner(min_prec, do_mode, pos, start_i);
1953        self.depth -= 1;
1954        result
1955    }
1956
1957    fn expr_prec_inner(
1958        &mut self,
1959        min_prec: u8,
1960        do_mode: DoExpressionMode,
1961        pos: Pos,
1962        start_i: usize,
1963    ) -> Expr {
1964        let mut lhs = match self.unary(do_mode) {
1965            Some(e) => e,
1966            None => {
1967                // Unparseable here: degrade to raw text up to the item end.
1968                let start = self.i;
1969                self.skip_to_item_end();
1970                if self.i == start {
1971                    self.bump();
1972                }
1973                return Expr::Error {
1974                    raw: self.slice_text(start),
1975                    span: self.node_span(start),
1976                    pos,
1977                };
1978            }
1979        };
1980        loop {
1981            let (op, prec, right_assoc) = match self.peek() {
1982                Some(TokenKind::Op(o)) => {
1983                    let o = o.clone();
1984                    if is_reserved_op(&o) {
1985                        // `e : Type` annotation: consume the type, keep e.
1986                        if o == ":" {
1987                            self.bump();
1988                            self.skip_type_tokens();
1989                            continue;
1990                        }
1991                        break;
1992                    }
1993                    let (p, r) = fixity(&o);
1994                    (o, p, r)
1995                }
1996                Some(TokenKind::Backtick) => {
1997                    // `e `div` e` — infix function application.
1998                    let name = match self.peek_at(1) {
1999                        Some(
2000                            TokenKind::LowerId { qualifier, name }
2001                            | TokenKind::UpperId { qualifier, name },
2002                        ) => qualifier
2003                            .as_ref()
2004                            .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
2005                        _ => break,
2006                    };
2007                    if self.peek_at(2) != Some(&TokenKind::Backtick) {
2008                        break;
2009                    }
2010                    (format!("`{name}`").into(), 9, false)
2011                }
2012                _ => break,
2013            };
2014            if prec < min_prec {
2015                break;
2016            }
2017            self.bump();
2018            if op.starts_with('`') {
2019                self.bump();
2020                self.bump();
2021            }
2022            let next_min = if right_assoc { prec } else { prec + 1 };
2023            let rhs = self.expr_prec(next_min, do_mode);
2024            lhs = Expr::BinOp {
2025                op,
2026                lhs: Box::new(lhs),
2027                rhs: Box::new(rhs),
2028                pos,
2029                span: self.node_span(start_i),
2030            };
2031        }
2032        lhs
2033    }
2034
2035    fn unary(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
2036        let pos = self.pos();
2037        let start_i = self.i;
2038        if self.at_op("-") {
2039            self.bump();
2040            let e = self.unary(do_mode)?;
2041            return Some(Expr::Neg {
2042                expr: Box::new(e),
2043                pos,
2044                span: self.node_span(start_i),
2045            });
2046        }
2047        self.application(do_mode)
2048    }
2049
2050    fn application(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
2051        let pos = self.pos();
2052        let start_i = self.i;
2053        let head0 = self.atom(do_mode)?;
2054        let mut head = self.projection_tail(head0);
2055        let mut args = Vec::new();
2056        loop {
2057            // Record syntax binds tighter than application:
2058            // `create Foo with x = 1` applies create to (Foo with {x = 1}).
2059            if self.at_keyword("with") {
2060                let target = args.pop().unwrap_or_else(|| {
2061                    std::mem::replace(
2062                        &mut head,
2063                        Expr::Error {
2064                            raw: String::new(),
2065                            pos,
2066                            span: Span::default(),
2067                        },
2068                    )
2069                });
2070                self.bump(); // with
2071                let fields = self.record_fields();
2072                let tpos = target.pos();
2073                let sp = Span::new(target.span().start, self.end_byte());
2074                let rec = Expr::Record {
2075                    base: Box::new(target),
2076                    fields,
2077                    pos: tpos,
2078                    span: sp,
2079                };
2080                if matches!(head, Expr::Error { ref raw, .. } if raw.is_empty()) {
2081                    head = rec;
2082                } else {
2083                    args.push(rec);
2084                }
2085                continue;
2086            }
2087            if !do_mode.allows_do() && self.at_keyword("do") {
2088                break;
2089            }
2090            // Type application `f @Type x` — consume and drop the type atom.
2091            if self.at_op("@") {
2092                self.bump();
2093                match self.peek() {
2094                    Some(TokenKind::UpperId { .. } | TokenKind::LowerId { .. }) => {
2095                        self.bump();
2096                    }
2097                    Some(TokenKind::LParen) => self.skip_balanced_parens(),
2098                    Some(TokenKind::LBracket) => {
2099                        let mut depth = 0usize;
2100                        while let Some(t) = self.peek() {
2101                            match t {
2102                                TokenKind::LBracket => depth += 1,
2103                                TokenKind::RBracket => {
2104                                    if depth == 0 {
2105                                        break;
2106                                    }
2107                                    depth -= 1;
2108                                    if depth == 0 {
2109                                        self.i += 1;
2110                                        break;
2111                                    }
2112                                }
2113                                _ => {}
2114                            }
2115                            self.i += 1;
2116                        }
2117                    }
2118                    _ => {}
2119                }
2120                continue;
2121            }
2122            match self.try_atom(do_mode) {
2123                Some(a) => args.push(self.projection_tail(a)),
2124                None => break,
2125            }
2126        }
2127        if args.is_empty() {
2128            Some(head)
2129        } else {
2130            Some(Expr::App {
2131                func: Box::new(head),
2132                args,
2133                pos,
2134                span: self.node_span(start_i),
2135            })
2136        }
2137    }
2138
2139    /// `{ f = e ; g ; .. }` after `with` (virtual block) or explicit braces.
2140    fn record_fields(&mut self) -> Vec<FieldAssign> {
2141        let mut fields = Vec::new();
2142        let explicit = self.at(&TokenKind::LBrace);
2143        if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
2144            return fields;
2145        }
2146        loop {
2147            while self.eat(&TokenKind::VSemi)
2148                || self.eat(&TokenKind::Semi)
2149                || self.eat(&TokenKind::Comma)
2150            {}
2151            match self.peek() {
2152                None => break,
2153                Some(TokenKind::VRBrace) if !explicit => {
2154                    self.bump();
2155                    break;
2156                }
2157                Some(TokenKind::RBrace) => {
2158                    self.bump();
2159                    break;
2160                }
2161                // Stray closer: discard so the loop always progresses.
2162                Some(TokenKind::RParen | TokenKind::RBracket) => {
2163                    self.bump();
2164                    continue;
2165                }
2166                _ => {}
2167            }
2168            let pos = self.pos();
2169            let start_i = self.i;
2170            if self.at_op("..") {
2171                self.bump();
2172                fields.push(FieldAssign {
2173                    name: "..".into(),
2174                    value: None,
2175                    pos,
2176                    span: self.node_span(start_i),
2177                });
2178                continue;
2179            }
2180            let name = match self.peek().cloned() {
2181                Some(TokenKind::LowerId {
2182                    qualifier: None,
2183                    name,
2184                }) => {
2185                    self.bump();
2186                    name
2187                }
2188                _ => {
2189                    self.skip_to_item_end();
2190                    continue;
2191                }
2192            };
2193            if self.eat_op("=") {
2194                let value = self.expr_prec(1, DoExpressionMode::Allow);
2195                fields.push(FieldAssign {
2196                    name,
2197                    value: Some(value),
2198                    pos,
2199                    span: self.node_span(start_i),
2200                });
2201            } else {
2202                // Pun: `Foo with owner`.
2203                fields.push(FieldAssign {
2204                    name,
2205                    value: None,
2206                    pos,
2207                    span: self.node_span(start_i),
2208                });
2209            }
2210        }
2211        fields
2212    }
2213
2214    fn try_atom(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
2215        match self.peek() {
2216            Some(TokenKind::LowerId { .. }) => {
2217                let kw = self.peek().and_then(|t| t.keyword());
2218                match kw {
2219                    // Block argument: `script do ...`, `submit p do ...`.
2220                    Some("do") if do_mode.allows_do() => self.atom(do_mode),
2221                    // Keywords that begin expressions are fine as atoms in
2222                    // head position but must not be slurped as arguments.
2223                    Some(
2224                        "if" | "case" | "do" | "let" | "try" | "where" | "then" | "else" | "of"
2225                        | "in" | "controller" | "with" | "catch",
2226                    ) => None,
2227                    _ => self.atom(do_mode),
2228                }
2229            }
2230            Some(
2231                TokenKind::UpperId { .. }
2232                | TokenKind::IntLit(_)
2233                | TokenKind::DecimalLit(_)
2234                | TokenKind::StringLit(_)
2235                | TokenKind::CharLit(_)
2236                | TokenKind::LParen
2237                | TokenKind::LBracket,
2238            ) => self.atom(do_mode),
2239            // Bare trailing lambda argument: `forA xs \x -> ...`.
2240            Some(TokenKind::Op(o)) if o.as_str() == "\\" => self.atom(do_mode),
2241            _ => None,
2242        }
2243    }
2244
2245    /// Fold tight (whitespace-free) `.field` record projections onto `base`.
2246    /// Projection binds tighter than application, so `length this.note` is
2247    /// `length (this.note)` not `(length this).note`, and a chain `a.b.c`
2248    /// left-nests. A *spaced* dot (`f . g`, composition) is not tight and is
2249    /// left to the binary-operator layer untouched. Qualified names
2250    /// (`Map.lookup`) are already a single token and never reach here.
2251    fn projection_tail(&mut self, mut base: Expr) -> Expr {
2252        while self.at_tight_projection() {
2253            let start = base.span().start;
2254            let pos = base.pos();
2255            self.bump(); // '.'
2256            let Some(field_tok) = self.bump() else {
2257                self.diag("expected projection field after '.'");
2258                return base;
2259            };
2260            let TokenKind::LowerId { qualifier, name } = field_tok.kind else {
2261                self.diag("expected projection field after '.'");
2262                return base;
2263            };
2264            let field = Expr::Var {
2265                qualifier,
2266                name,
2267                pos: field_tok.pos,
2268                span: Span::new(field_tok.start, field_tok.end),
2269            };
2270            base = Expr::BinOp {
2271                op: ".".into(),
2272                lhs: Box::new(base),
2273                rhs: Box::new(field),
2274                pos,
2275                span: Span::new(start, self.end_byte()),
2276            };
2277        }
2278        base
2279    }
2280
2281    /// True when the cursor sits on a `.` that abuts a real token on its left
2282    /// and an unqualified lowercase field on its right with no whitespace on
2283    /// either side — i.e. a record projection, not function composition.
2284    fn at_tight_projection(&self) -> bool {
2285        if self.i == 0 {
2286            return false;
2287        }
2288        let dot = match self.toks.get(self.i) {
2289            Some(t) => t,
2290            None => return false,
2291        };
2292        if !matches!(&dot.kind, TokenKind::Op(o) if o.as_str() == ".") {
2293            return false;
2294        }
2295        // Tight on the left: the dot abuts the base's last byte. The previous
2296        // token must be real — a virtual layout token here means a newline or
2297        // dedent sat between base and dot, which can never be a tight dot.
2298        let prev = &self.toks[self.i - 1];
2299        if prev.is_virtual() || prev.end != dot.start {
2300            return false;
2301        }
2302        // Tight on the right: an unqualified lowercase field abuts the dot.
2303        self.toks.get(self.i + 1).is_some_and(|t| {
2304            matches!(
2305                &t.kind,
2306                TokenKind::LowerId {
2307                    qualifier: None,
2308                    ..
2309                }
2310            ) && t.start == dot.end
2311        })
2312    }
2313
2314    fn atom(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
2315        let pos = self.pos();
2316        let start_i = self.i;
2317        match self.peek().cloned() {
2318            Some(TokenKind::LowerId { qualifier, name }) => {
2319                match name.as_str() {
2320                    "if" if qualifier.is_none() => return self.if_expr(),
2321                    "case" if qualifier.is_none() => return self.case_expr(),
2322                    "do" if qualifier.is_none() => {
2323                        if !do_mode.allows_do() {
2324                            return None;
2325                        }
2326                        return self.do_expr();
2327                    }
2328                    "let" if qualifier.is_none() => return self.let_expr(),
2329                    "try" if qualifier.is_none() => return self.try_expr(),
2330                    _ => {}
2331                }
2332                self.bump();
2333                Some(Expr::Var {
2334                    qualifier,
2335                    name,
2336                    pos,
2337                    span: self.node_span(start_i),
2338                })
2339            }
2340            Some(TokenKind::UpperId { qualifier, name }) => {
2341                self.bump();
2342                let base = Expr::Con {
2343                    qualifier,
2344                    name,
2345                    pos,
2346                    span: self.node_span(start_i),
2347                };
2348                // Explicit-brace record syntax: `Foo {x = 1}`.
2349                if self.at(&TokenKind::LBrace) {
2350                    let fields = self.record_fields();
2351                    return Some(Expr::Record {
2352                        base: Box::new(base),
2353                        fields,
2354                        pos,
2355                        span: self.node_span(start_i),
2356                    });
2357                }
2358                Some(base)
2359            }
2360            Some(TokenKind::IntLit(text)) => {
2361                self.bump();
2362                Some(Expr::Lit {
2363                    kind: LitKind::Int,
2364                    text,
2365                    pos,
2366                    span: self.node_span(start_i),
2367                })
2368            }
2369            Some(TokenKind::DecimalLit(text)) => {
2370                self.bump();
2371                Some(Expr::Lit {
2372                    kind: LitKind::Decimal,
2373                    text,
2374                    pos,
2375                    span: self.node_span(start_i),
2376                })
2377            }
2378            Some(TokenKind::StringLit(text)) => {
2379                self.bump();
2380                Some(Expr::Lit {
2381                    kind: LitKind::Text,
2382                    text,
2383                    pos,
2384                    span: self.node_span(start_i),
2385                })
2386            }
2387            Some(TokenKind::CharLit(text)) => {
2388                self.bump();
2389                Some(Expr::Lit {
2390                    kind: LitKind::Char,
2391                    text,
2392                    pos,
2393                    span: self.node_span(start_i),
2394                })
2395            }
2396            Some(TokenKind::Op(o)) if o.as_str() == "\\" => self.lambda_expr(),
2397            Some(TokenKind::LParen) => self.paren_expr(),
2398            Some(TokenKind::LBracket) => self.list_expr(),
2399            _ => None,
2400        }
2401    }
2402
2403    fn if_expr(&mut self) -> Option<Expr> {
2404        let pos = self.pos();
2405        let start_i = self.i;
2406        self.bump(); // if
2407        let cond = self.expr();
2408        self.eat(&TokenKind::VSemi); // DoAndIfThenElse style
2409        if !self.eat_keyword("then") {
2410            self.diag("expected 'then'");
2411            return Some(Expr::Error {
2412                raw: format!("if {}", cond.render()),
2413                pos,
2414                span: self.node_span(start_i),
2415            });
2416        }
2417        let then_branch = self.expr();
2418        self.eat(&TokenKind::VSemi);
2419        if !self.eat_keyword("else") {
2420            self.diag("expected 'else'");
2421            return Some(Expr::Error {
2422                raw: format!("if {} then {}", cond.render(), then_branch.render()),
2423                pos,
2424                span: self.node_span(start_i),
2425            });
2426        }
2427        let else_branch = self.expr();
2428        Some(Expr::If {
2429            cond: Box::new(cond),
2430            then_branch: Box::new(then_branch),
2431            else_branch: Box::new(else_branch),
2432            pos,
2433            span: self.node_span(start_i),
2434        })
2435    }
2436
2437    fn case_expr(&mut self) -> Option<Expr> {
2438        let pos = self.pos();
2439        let start_i = self.i;
2440        self.bump(); // case
2441        let scrutinee = self.expr_no_do();
2442        if !self.eat_keyword("of") {
2443            self.diag("expected 'of' in case expression");
2444            return Some(Expr::Error {
2445                raw: format!("case {}", scrutinee.render()),
2446                pos,
2447                span: self.node_span(start_i),
2448            });
2449        }
2450        let mut alts = Vec::new();
2451        if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
2452            loop {
2453                while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
2454                match self.peek() {
2455                    None => break,
2456                    Some(TokenKind::VRBrace | TokenKind::RBrace) => {
2457                        self.bump();
2458                        break;
2459                    }
2460                    // Stray closer: discard so the loop always progresses.
2461                    Some(TokenKind::RParen | TokenKind::RBracket) => {
2462                        self.bump();
2463                        continue;
2464                    }
2465                    _ => {}
2466                }
2467                // An alternative can carry a `where` block for its body.
2468                if self.eat_keyword("where") {
2469                    let _ = self.binding_block();
2470                    continue;
2471                }
2472                match self.case_alt() {
2473                    Some(a) => alts.push(a),
2474                    None => self.skip_to_item_end(),
2475                }
2476            }
2477        }
2478        Some(Expr::Case {
2479            scrutinee: Box::new(scrutinee),
2480            alts,
2481            pos,
2482            span: self.node_span(start_i),
2483        })
2484    }
2485
2486    fn case_alt(&mut self) -> Option<Alt> {
2487        let pos = self.pos();
2488        let start_i = self.i;
2489        let pat = self.pattern()?;
2490        if self.at_op("|") {
2491            // Guarded alternative(s): take the first body, consume all.
2492            // Each guard is comma-separated qualifiers, each a boolean
2493            // expression or a pattern guard `pat <- expr`.
2494            let mut first: Option<Expr> = None;
2495            while self.eat_op("|") {
2496                loop {
2497                    let _guard = self.expr();
2498                    if self.eat_op("<-") {
2499                        let _ = self.expr();
2500                    }
2501                    if !self.eat(&TokenKind::Comma) {
2502                        break;
2503                    }
2504                }
2505                if !self.eat_op("->") {
2506                    self.diag("expected '->' in guarded case alternative");
2507                    return None;
2508                }
2509                let body = self.expr();
2510                if first.is_none() {
2511                    first = Some(body);
2512                }
2513            }
2514            return Some(Alt {
2515                pat,
2516                body: first?,
2517                pos,
2518                span: self.node_span(start_i),
2519            });
2520        }
2521        if !self.eat_op("->") {
2522            self.diag("expected '->' in case alternative");
2523            return None;
2524        }
2525        let body = self.expr();
2526        Some(Alt {
2527            pat,
2528            body,
2529            pos,
2530            span: self.node_span(start_i),
2531        })
2532    }
2533
2534    fn do_expr(&mut self) -> Option<Expr> {
2535        let pos = self.pos();
2536        let start_i = self.i;
2537        self.bump(); // do
2538        let mut stmts = Vec::new();
2539        if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
2540            loop {
2541                while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
2542                match self.peek() {
2543                    None => break,
2544                    Some(TokenKind::VRBrace | TokenKind::RBrace) => {
2545                        self.bump();
2546                        break;
2547                    }
2548                    // Stray closer: discard so the loop always progresses.
2549                    Some(TokenKind::RParen | TokenKind::RBracket) => {
2550                        self.bump();
2551                        continue;
2552                    }
2553                    _ => {}
2554                }
2555                stmts.push(self.do_stmt());
2556            }
2557        }
2558        Some(Expr::Do {
2559            stmts,
2560            pos,
2561            span: self.node_span(start_i),
2562        })
2563    }
2564
2565    fn do_stmt(&mut self) -> DoStmt {
2566        let pos = self.pos();
2567        let start_i = self.i;
2568        if self.at_keyword("let") {
2569            self.bump();
2570            let bindings = self.binding_block();
2571            // `let ... in body` as a statement is an expression.
2572            if self.eat_keyword("in") {
2573                let body = self.expr();
2574                return DoStmt::Expr {
2575                    expr: Expr::LetIn {
2576                        bindings,
2577                        body: Box::new(body),
2578                        pos,
2579                        span: self.node_span(start_i),
2580                    },
2581                    pos,
2582                    span: self.node_span(start_i),
2583                };
2584            }
2585            return DoStmt::Let {
2586                bindings,
2587                pos,
2588                span: self.node_span(start_i),
2589            };
2590        }
2591        // Try `pat <- expr` with rollback.
2592        let snapshot = self.i;
2593        if let Some(pat) = self.try_bind_pattern() {
2594            if self.at_op("<-") {
2595                self.bump();
2596                let expr = self.expr();
2597                return DoStmt::Bind {
2598                    pat,
2599                    expr,
2600                    pos,
2601                    span: self.node_span(start_i),
2602                };
2603            }
2604        }
2605        self.i = snapshot;
2606        let expr = self.expr();
2607        DoStmt::Expr {
2608            expr,
2609            pos,
2610            span: self.node_span(start_i),
2611        }
2612    }
2613
2614    /// Pattern attempt for `pat <- ...`; restores nothing itself (caller
2615    /// rolls back on failure).
2616    fn try_bind_pattern(&mut self) -> Option<Pat> {
2617        self.pattern()
2618    }
2619
2620    fn let_expr(&mut self) -> Option<Expr> {
2621        let pos = self.pos();
2622        let start_i = self.i;
2623        self.bump(); // let
2624        let bindings = self.binding_block();
2625        if self.eat_keyword("in") {
2626            let body = self.expr();
2627            return Some(Expr::LetIn {
2628                bindings,
2629                body: Box::new(body),
2630                pos,
2631                span: self.node_span(start_i),
2632            });
2633        }
2634        // `let` without `in` outside a do block — degrade gracefully.
2635        Some(Expr::LetIn {
2636            bindings,
2637            body: Box::new(Expr::Error {
2638                raw: String::new(),
2639                pos,
2640                span: self.node_span(start_i),
2641            }),
2642            pos,
2643            span: self.node_span(start_i),
2644        })
2645    }
2646
2647    fn try_expr(&mut self) -> Option<Expr> {
2648        let pos = self.pos();
2649        let start_i = self.i;
2650        self.bump(); // try
2651        let body = self.expr();
2652        let mut handlers = Vec::new();
2653        self.eat(&TokenKind::VSemi);
2654        if self.eat_keyword("catch") {
2655            if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
2656                loop {
2657                    while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
2658                    match self.peek() {
2659                        None => break,
2660                        Some(TokenKind::VRBrace | TokenKind::RBrace) => {
2661                            self.bump();
2662                            break;
2663                        }
2664                        // Stray closer: discard so the loop always progresses.
2665                        Some(TokenKind::RParen | TokenKind::RBracket) => {
2666                            self.bump();
2667                            continue;
2668                        }
2669                        _ => {}
2670                    }
2671                    match self.case_alt() {
2672                        Some(a) => handlers.push(a),
2673                        None => self.skip_to_item_end(),
2674                    }
2675                }
2676            } else if let Some(a) = self.case_alt() {
2677                // Single-alternative catch on the same line.
2678                handlers.push(a);
2679            }
2680        }
2681        Some(Expr::Try {
2682            body: Box::new(body),
2683            handlers,
2684            pos,
2685            span: self.node_span(start_i),
2686        })
2687    }
2688
2689    fn lambda_expr(&mut self) -> Option<Expr> {
2690        let pos = self.pos();
2691        let start_i = self.i;
2692        self.bump(); // backslash
2693                     // `\case` — lambda-case: one implicit argument matched by the alts.
2694        if self.eat_keyword("case") {
2695            let mut alts = Vec::new();
2696            if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
2697                loop {
2698                    while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
2699                    match self.peek() {
2700                        None => break,
2701                        Some(TokenKind::VRBrace | TokenKind::RBrace) => {
2702                            self.bump();
2703                            break;
2704                        }
2705                        Some(TokenKind::RParen | TokenKind::RBracket) => {
2706                            self.bump();
2707                            continue;
2708                        }
2709                        _ => {}
2710                    }
2711                    match self.case_alt() {
2712                        Some(a) => alts.push(a),
2713                        None => self.skip_to_item_end(),
2714                    }
2715                }
2716            }
2717            return Some(Expr::Lambda {
2718                params: vec![Pat::Var {
2719                    name: "_".into(),
2720                    pos,
2721                    span: Span::new(self.byte_at(start_i), self.byte_at(start_i)),
2722                }],
2723                body: Box::new(Expr::Case {
2724                    scrutinee: Box::new(Expr::Var {
2725                        qualifier: None,
2726                        name: "_".into(),
2727                        pos,
2728                        span: Span::new(self.byte_at(start_i), self.byte_at(start_i)),
2729                    }),
2730                    alts,
2731                    pos,
2732                    span: self.node_span(start_i),
2733                }),
2734                pos,
2735                span: self.node_span(start_i),
2736            });
2737        }
2738        let mut params = Vec::new();
2739        while !self.at_op("->") {
2740            match self.pattern_atom() {
2741                Some(p) => params.push(p),
2742                None => {
2743                    self.diag("bad lambda parameter");
2744                    let start = self.i;
2745                    self.skip_to_item_end();
2746                    return Some(Expr::Error {
2747                        raw: format!("\\{}", self.slice_text(start)),
2748                        pos,
2749                        span: self.node_span(start_i),
2750                    });
2751                }
2752            }
2753        }
2754        self.bump(); // ->
2755        let body = self.expr();
2756        Some(Expr::Lambda {
2757            params,
2758            body: Box::new(body),
2759            pos,
2760            span: self.node_span(start_i),
2761        })
2762    }
2763
2764    fn paren_expr(&mut self) -> Option<Expr> {
2765        let pos = self.pos();
2766        let start_i = self.i;
2767        self.bump(); // (
2768        if self.eat(&TokenKind::RParen) {
2769            return Some(Expr::Con {
2770                qualifier: None,
2771                name: "()".into(),
2772                pos,
2773                span: self.node_span(start_i),
2774            });
2775        }
2776        // Operator section / operator reference: `(+)`, `(+ 1)`.
2777        if let Some(TokenKind::Op(o)) = self.peek().cloned() {
2778            if !is_reserved_op(&o) && o != "\\" && o != "-" {
2779                self.bump();
2780                if self.eat(&TokenKind::RParen) {
2781                    return Some(Expr::Section {
2782                        op: o,
2783                        operand: None,
2784                        side: SectionSide::Right,
2785                        pos,
2786                        span: self.node_span(start_i),
2787                    });
2788                }
2789                let operand = self.expr();
2790                self.eat(&TokenKind::RParen);
2791                return Some(Expr::Section {
2792                    op: o,
2793                    operand: Some(Box::new(operand)),
2794                    side: SectionSide::Right,
2795                    pos,
2796                    span: self.node_span(start_i),
2797                });
2798            }
2799        }
2800        let first = self.expr();
2801        if self.at(&TokenKind::Comma) {
2802            let mut items = vec![first];
2803            while self.eat(&TokenKind::Comma) {
2804                items.push(self.expr());
2805            }
2806            self.eat(&TokenKind::RParen);
2807            return Some(Expr::Tuple {
2808                items,
2809                pos,
2810                span: self.node_span(start_i),
2811            });
2812        }
2813        // Left section: `(x +)`.
2814        if let Some(TokenKind::Op(o)) = self.peek().cloned() {
2815            if !is_reserved_op(&o) && self.peek_at(1) == Some(&TokenKind::RParen) {
2816                self.bump();
2817                self.bump();
2818                return Some(Expr::Section {
2819                    op: o,
2820                    operand: Some(Box::new(first)),
2821                    side: SectionSide::Left,
2822                    pos,
2823                    span: self.node_span(start_i),
2824                });
2825            }
2826        }
2827        self.eat(&TokenKind::RParen);
2828        Some(first)
2829    }
2830
2831    fn list_expr(&mut self) -> Option<Expr> {
2832        let pos = self.pos();
2833        let start_i = self.i;
2834        self.bump(); // [
2835        let mut items = Vec::new();
2836        if self.eat(&TokenKind::RBracket) {
2837            return Some(Expr::List {
2838                items,
2839                pos,
2840                span: self.node_span(start_i),
2841            });
2842        }
2843        loop {
2844            let e = self.expr();
2845            // Range: `[a .. b]` / `[a ..]`.
2846            if self.at_op("..") {
2847                self.bump();
2848                let hi = if self.at(&TokenKind::RBracket) {
2849                    Expr::Error {
2850                        raw: String::new(),
2851                        pos,
2852                        span: self.node_span(start_i),
2853                    }
2854                } else {
2855                    self.expr()
2856                };
2857                self.eat(&TokenKind::RBracket);
2858                return Some(Expr::BinOp {
2859                    op: "..".into(),
2860                    lhs: Box::new(e),
2861                    rhs: Box::new(hi),
2862                    pos,
2863                    span: self.node_span(start_i),
2864                });
2865            }
2866            // List comprehension: degrade the qualifier part to raw text.
2867            if self.at_op("|") {
2868                let start = self.i;
2869                let mut brackets = 1usize;
2870                while let Some(t) = self.peek() {
2871                    match t {
2872                        TokenKind::LBracket => brackets += 1,
2873                        TokenKind::RBracket => {
2874                            brackets -= 1;
2875                            if brackets == 0 {
2876                                break;
2877                            }
2878                        }
2879                        TokenKind::VSemi | TokenKind::VRBrace => break,
2880                        _ => {}
2881                    }
2882                    self.i += 1;
2883                }
2884                let raw = self.slice_text(start);
2885                self.eat(&TokenKind::RBracket);
2886                return Some(Expr::App {
2887                    func: Box::new(e),
2888                    args: vec![Expr::Error {
2889                        raw,
2890                        pos,
2891                        span: self.node_span(start_i),
2892                    }],
2893                    pos,
2894                    span: self.node_span(start_i),
2895                });
2896            }
2897            items.push(e);
2898            if !self.eat(&TokenKind::Comma) {
2899                break;
2900            }
2901        }
2902        self.eat(&TokenKind::RBracket);
2903        Some(Expr::List {
2904            items,
2905            pos,
2906            span: self.node_span(start_i),
2907        })
2908    }
2909}
2910
2911/// Operators that structure declarations and can never be expression infix
2912/// operators.
2913fn is_reserved_op(op: &str) -> bool {
2914    matches!(op, "=" | "<-" | "->" | "|" | ":" | "=>" | "@" | "\\" | "..")
2915}
2916
2917/// (precedence, right-assoc) — Haskell defaults; unknown operators get
2918/// infixl 9.
2919fn fixity(op: &str) -> (u8, bool) {
2920    match op {
2921        "$" | "$!" => (1, true),
2922        ">>=" | ">>" | "=<<" | "<&>" => (2, false),
2923        "||" => (3, true),
2924        "&&" => (4, true),
2925        "==" | "/=" | "<" | "<=" | ">" | ">=" => (5, false),
2926        "::" | "++" | "<>" => (6, true),
2927        "+" | "-" => (7, false),
2928        "*" | "/" => (8, false),
2929        "^" | "**" => (9, true),
2930        "." | "!!" => (10, true),
2931        _ => (9, false),
2932    }
2933}
2934
2935/// Merge type signatures and successive equations of the same function into
2936/// one `Decl::Function`, preserving first-seen order.
2937/// Bounding span of a function's equations (their first start to last end).
2938/// `None` for a signature-only function (no equations yet).
2939fn equations_extent(eqs: &[Equation]) -> Option<Span> {
2940    let mut it = eqs.iter();
2941    let first = it.next()?;
2942    let mut s = first.span;
2943    for e in it {
2944        s.start = s.start.min(e.span.start);
2945        s.end = s.end.max(e.span.end);
2946    }
2947    Some(s)
2948}
2949
2950fn merge_functions(decls: &mut Vec<Decl>) {
2951    let mut out: Vec<Decl> = Vec::with_capacity(decls.len());
2952    let mut function_index_by_name: HashMap<Identifier, usize> = HashMap::new();
2953    for decl in decls.drain(..) {
2954        match decl {
2955            Decl::Function(f) => {
2956                if let Some(existing_index) = function_index_by_name.get(&f.name).copied() {
2957                    let Decl::Function(g) = &mut out[existing_index] else {
2958                        out.push(Decl::Function(f));
2959                        continue;
2960                    };
2961                    if g.ty.is_none() {
2962                        g.ty = f.ty.clone();
2963                    }
2964                    if g.sig_span.is_none() {
2965                        g.sig_span = f.sig_span;
2966                    }
2967                    // The function's reported position is its first equation,
2968                    // not its type signature.
2969                    if g.equations.is_empty() && !f.equations.is_empty() {
2970                        g.pos = f.pos;
2971                    }
2972                    g.equations.extend(f.equations);
2973                    // A function's span is the extent of its equations, which
2974                    // are contiguous in well-formed source. A type signature
2975                    // can sit apart (with unrelated decls in between), so it is
2976                    // tracked in `sig_span` and never folded into `span` —
2977                    // doing so could straddle a sibling decl and break the
2978                    // nesting invariant.
2979                    g.span = equations_extent(&g.equations)
2980                        .or(g.sig_span)
2981                        .unwrap_or(g.span);
2982                } else {
2983                    function_index_by_name.insert(f.name.clone(), out.len());
2984                    out.push(Decl::Function(f));
2985                }
2986            }
2987            other => out.push(other),
2988        }
2989    }
2990    *decls = out;
2991}
2992
2993/// Parse a type from a slice of the type's tokens (e.g. the tokens between a
2994/// field's `:` and the item end). PURE: it never touches the main parser cursor
2995/// and never affects any span, so it is invisible to daml-fmt. Returns `None`
2996/// when the whole slice does not parse cleanly as a type.
2997///
2998/// Grammar (precedence low → high): constraint `C => T`, function `a -> b`
2999/// (right-assoc), application `head arg...` (left-assoc), then atoms (`Con`,
3000/// `Var`, `[T]`, `()`, `(T)`, `(a, b)`).
3001pub(crate) fn parse_type_from_tokens(tokens: &[Token]) -> Option<Type> {
3002    // Virtual layout tokens carry no type meaning; drop them so a stray VSemi
3003    // in the slice can't sink an otherwise-clean parse.
3004    let real_tokens: Vec<&Token> = tokens.iter().filter(|t| !t.is_virtual()).collect();
3005    if real_tokens.is_empty() {
3006        return None;
3007    }
3008    let mut parser = TypeTokenParser {
3009        tokens: &real_tokens,
3010        cursor: 0,
3011    };
3012    let ty = parser.parse_type()?;
3013    // Require the whole slice to be consumed: a partial parse means the type
3014    // had a shape we don't model, so report unknown rather than a half-truth.
3015    if parser.cursor == real_tokens.len() {
3016        Some(ty)
3017    } else {
3018        None
3019    }
3020}
3021
3022struct TypeTokenParser<'a> {
3023    tokens: &'a [&'a Token],
3024    cursor: usize,
3025}
3026
3027#[derive(Debug)]
3028struct FieldBlock {
3029    fields: Vec<FieldDecl>,
3030    dangling: bool,
3031}
3032
3033/// Result of parsing one atom: a real type, or a dropped type-level nat literal
3034/// (`Numeric 10` — not a type, so it never enters the App arg list).
3035enum TypeAtom {
3036    ParsedType(Type),
3037    DroppedLiteral(Span),
3038}
3039
3040impl<'a> TypeTokenParser<'a> {
3041    fn peek(&self) -> Option<&'a Token> {
3042        self.tokens.get(self.cursor).copied()
3043    }
3044
3045    fn eat_op(&mut self, op: &str) -> bool {
3046        if self.peek().is_some_and(|t| t.kind.is_op(op)) {
3047            self.cursor += 1;
3048            true
3049        } else {
3050            false
3051        }
3052    }
3053
3054    /// Full type: a constraint context `=> body`, or a function `a -> b`, or a
3055    /// bare application.
3056    fn parse_type(&mut self) -> Option<Type> {
3057        let lhs = if self.eat_keyword("forall") {
3058            self.parse_forall_type()?
3059        } else {
3060            self.parse_application_type()?
3061        };
3062        if self.eat_op("=>") {
3063            // `lhs` was the constraint context; drop it, keep the body.
3064            let body = self.parse_type()?;
3065            let span = Span::new(lhs.span().start, body.span().end);
3066            return Some(Type::Constrained(Box::new(body), span));
3067        }
3068        if self.eat_op("->") {
3069            let rhs = self.parse_type()?;
3070            let span = Span::new(lhs.span().start, rhs.span().end);
3071            return Some(Type::Fun(Box::new(lhs), Box::new(rhs), span));
3072        }
3073        Some(lhs)
3074    }
3075
3076    /// Application spine: one head atom applied to zero or more argument atoms.
3077    fn parse_application_type(&mut self) -> Option<Type> {
3078        let head = match self.parse_atom()? {
3079            TypeAtom::ParsedType(t) => t,
3080            // A bare nat literal is not a type.
3081            TypeAtom::DroppedLiteral(_) => return None,
3082        };
3083        let mut args = Vec::new();
3084        let start = head.span().start;
3085        let mut end = head.span().end;
3086        loop {
3087            // Only continue the spine if the next token can START an atom; an
3088            // operator (`->`, `=>`) or closer ends it.
3089            if !self.is_at_type_atom_start() {
3090                break;
3091            }
3092            match self.parse_atom()? {
3093                TypeAtom::ParsedType(t) => {
3094                    end = t.span().end;
3095                    args.push(t);
3096                }
3097                TypeAtom::DroppedLiteral(span) => {
3098                    // `Numeric 10` — drop the `10` as structure but keep it in
3099                    // the enclosing type span.
3100                    end = span.end;
3101                }
3102            }
3103        }
3104        let span = Span::new(start, end);
3105        if args.is_empty() {
3106            Some(head.with_span(span))
3107        } else {
3108            Some(Type::App(Box::new(head), args, span))
3109        }
3110    }
3111
3112    /// True if the current token can begin an atom (so the application spine
3113    /// should keep going).
3114    fn is_at_type_atom_start(&self) -> bool {
3115        matches!(
3116            self.peek().map(|t| &t.kind),
3117            Some(
3118                TokenKind::UpperId { .. }
3119                    | TokenKind::LowerId { .. }
3120                    | TokenKind::IntLit(_)
3121                    | TokenKind::DecimalLit(_)
3122                    | TokenKind::StringLit(_)
3123                    | TokenKind::CharLit(_)
3124                    | TokenKind::LBracket
3125                    | TokenKind::LParen
3126            )
3127        )
3128    }
3129
3130    fn parse_atom(&mut self) -> Option<TypeAtom> {
3131        let tok = self.peek()?;
3132        match &tok.kind {
3133            TokenKind::UpperId { qualifier, name } => {
3134                let con = Type::Con {
3135                    qualifier: qualifier.clone(),
3136                    name: name.clone(),
3137                    span: Span::new(tok.start, tok.end),
3138                };
3139                self.cursor += 1;
3140                Some(TypeAtom::ParsedType(con))
3141            }
3142            TokenKind::LowerId { name, .. } => {
3143                // Type variable (`a`, `n`). Qualified lowercase never appears in
3144                // a real type position; treat the name as the variable.
3145                let var = Type::Var(name.clone(), Span::new(tok.start, tok.end));
3146                self.cursor += 1;
3147                Some(TypeAtom::ParsedType(var))
3148            }
3149            TokenKind::IntLit(_) | TokenKind::DecimalLit(_) => {
3150                // Type-level nat literal (`Numeric 10`): consumed, but dropped.
3151                self.cursor += 1;
3152                Some(TypeAtom::DroppedLiteral(Span::new(tok.start, tok.end)))
3153            }
3154            TokenKind::StringLit(_) | TokenKind::CharLit(_) => {
3155                // Type arguments like `HasField \"x\"` are not type-shape; keep
3156                // the token for span recovery.
3157                self.cursor += 1;
3158                Some(TypeAtom::DroppedLiteral(Span::new(tok.start, tok.end)))
3159            }
3160            TokenKind::LBracket => {
3161                let start = tok.start;
3162                self.cursor += 1;
3163                let inner = self.parse_type()?;
3164                self.eat_token(&TokenKind::RBracket).map(|end| {
3165                    TypeAtom::ParsedType(Type::List(Box::new(inner), Span::new(start, end.end)))
3166                })
3167            }
3168            TokenKind::LParen => {
3169                let start = tok.start;
3170                self.cursor += 1;
3171                if let Some(op) = self.eat_token_if_operator() {
3172                    let mut name = op.as_str().to_string();
3173                    while matches!(
3174                        self.tokens.get(self.cursor).map(|t| &t.kind),
3175                        Some(TokenKind::Op(_))
3176                    ) {
3177                        self.cursor += 1;
3178                        if let TokenKind::Op(o) = &self.tokens[self.cursor - 1].kind {
3179                            name.push_str(o.as_str());
3180                        }
3181                    }
3182                    if self.eat_token(&TokenKind::RParen).is_some()
3183                        && self
3184                            .tokens
3185                            .get(self.cursor)
3186                            .is_some_and(|t| Self::is_type_atom_start(&t.kind))
3187                    {
3188                        let end = self.tokens[self.cursor - 1];
3189                        return Some(TypeAtom::ParsedType(Type::Con {
3190                            qualifier: None,
3191                            name: name.into(),
3192                            span: Span::new(start, end.end),
3193                        }));
3194                    }
3195                    return None;
3196                }
3197                if matches!(
3198                    self.tokens.get(self.cursor).map(|t| &t.kind),
3199                    Some(TokenKind::Comma)
3200                ) {
3201                    let mut name = String::from(",");
3202                    self.cursor += 1;
3203                    while matches!(
3204                        self.tokens.get(self.cursor).map(|t| &t.kind),
3205                        Some(TokenKind::Comma)
3206                    ) {
3207                        self.cursor += 1;
3208                        name.push(',');
3209                    }
3210                    if self.eat_token(&TokenKind::RParen).is_some()
3211                        && self
3212                            .tokens
3213                            .get(self.cursor)
3214                            .is_some_and(|t| Self::is_type_atom_start(&t.kind))
3215                    {
3216                        let end = self.tokens[self.cursor - 1];
3217                        return Some(TypeAtom::ParsedType(Type::Con {
3218                            qualifier: None,
3219                            name: name.into(),
3220                            span: Span::new(start, end.end),
3221                        }));
3222                    }
3223                    return None;
3224                }
3225                if let Some(end) = self.eat_token(&TokenKind::RParen) {
3226                    // ()
3227                    return Some(TypeAtom::ParsedType(Type::Unit(Span::new(start, end.end))));
3228                }
3229                let first = self.parse_type()?;
3230                if self.peek().map(|t| &t.kind) == Some(&TokenKind::Comma) {
3231                    let mut items = vec![first];
3232                    while self.eat_token(&TokenKind::Comma).is_some() {
3233                        items.push(self.parse_type()?);
3234                    }
3235                    self.eat_token(&TokenKind::RParen).map(|end| {
3236                        TypeAtom::ParsedType(Type::Tuple(items, Span::new(start, end.end)))
3237                    })
3238                } else {
3239                    self.eat_token(&TokenKind::RParen).map(|end| {
3240                        // Grouping parens.
3241                        TypeAtom::ParsedType(first.with_span(Span::new(start, end.end)))
3242                    })
3243                }
3244            }
3245            _ => None,
3246        }
3247    }
3248
3249    fn eat_keyword(&mut self, kw: &str) -> bool {
3250        if self.peek().is_some_and(|t| t.kind.is_keyword(kw)) {
3251            self.cursor += 1;
3252            true
3253        } else {
3254            false
3255        }
3256    }
3257
3258    fn parse_forall_type(&mut self) -> Option<Type> {
3259        let start = self
3260            .tokens
3261            .get(self.cursor.wrapping_sub(1))
3262            .map(|t| t.start)
3263            .unwrap_or_default();
3264        while self.cursor < self.tokens.len() {
3265            if self.peek().is_some_and(|t| t.kind.is_op(".")) {
3266                self.cursor += 1;
3267                let body = self.parse_type()?;
3268                let body_span = body.span();
3269                return Some(body.with_span(Span::new(start, body_span.end)));
3270            }
3271            self.cursor += 1;
3272        }
3273        None
3274    }
3275
3276    fn eat_token(&mut self, tok: &TokenKind) -> Option<&'a Token> {
3277        if self.peek().is_some_and(|t| t.kind == *tok) {
3278            let t = self.peek();
3279            self.cursor += 1;
3280            t
3281        } else {
3282            None
3283        }
3284    }
3285
3286    fn eat_token_if_operator(&mut self) -> Option<&'a Operator> {
3287        match self.peek() {
3288            Some(Token {
3289                kind: TokenKind::Op(op),
3290                ..
3291            }) => {
3292                self.cursor += 1;
3293                Some(op)
3294            }
3295            _ => None,
3296        }
3297    }
3298
3299    const fn is_type_atom_start(kind: &TokenKind) -> bool {
3300        matches!(
3301            kind,
3302            TokenKind::UpperId { .. }
3303                | TokenKind::LowerId { .. }
3304                | TokenKind::IntLit(_)
3305                | TokenKind::DecimalLit(_)
3306                | TokenKind::StringLit(_)
3307                | TokenKind::CharLit(_)
3308                | TokenKind::LParen
3309                | TokenKind::LBracket
3310        )
3311    }
3312}
3313
3314fn render_token_slice(tokens: &[Token]) -> String {
3315    let mut s = String::new();
3316    let mut prev_no_space_after = true;
3317    for t in tokens {
3318        let (text, no_space_before, no_space_after): (String, bool, bool) = match &t.kind {
3319            TokenKind::LowerId { qualifier, name } | TokenKind::UpperId { qualifier, name } => (
3320                qualifier
3321                    .as_ref()
3322                    .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
3323                false,
3324                false,
3325            ),
3326            TokenKind::Op(o) => (o.to_string(), false, false),
3327            TokenKind::IntLit(n) | TokenKind::DecimalLit(n) => (n.clone(), false, false),
3328            TokenKind::StringLit(v) => (format!("{v:?}"), false, false),
3329            TokenKind::CharLit(v) => (format!("'{v}'"), false, false),
3330            TokenKind::LParen => ("(".to_string(), false, true),
3331            TokenKind::RParen => (")".to_string(), true, false),
3332            TokenKind::LBracket => ("[".to_string(), false, true),
3333            TokenKind::RBracket => ("]".to_string(), true, false),
3334            TokenKind::LBrace => ("{".to_string(), false, true),
3335            TokenKind::RBrace => ("}".to_string(), true, false),
3336            TokenKind::Comma => (",".to_string(), true, false),
3337            TokenKind::Semi | TokenKind::VSemi => (";".to_string(), true, false),
3338            TokenKind::Backtick => ("`".to_string(), false, false),
3339            TokenKind::VLBrace | TokenKind::VRBrace => continue,
3340        };
3341        if !s.is_empty() && !no_space_before && !prev_no_space_after {
3342            s.push(' ');
3343        }
3344        s.push_str(&text);
3345        prev_no_space_after = no_space_after;
3346    }
3347    s
3348}
3349
3350#[cfg(test)]
3351mod type_tests {
3352    use super::*;
3353    use crate::lexer::lex;
3354
3355    /// Parse a bare type string straight through the lexer. A single-line type
3356    /// has no layout-significant newlines, so no virtual tokens appear — this
3357    /// exercises the type grammar in isolation.
3358    fn ty(s: &str) -> Option<Type> {
3359        let (toks, errs) = lex(s).into_parts();
3360        assert!(errs.is_empty(), "lex errors for {s:?}: {errs:?}");
3361        parse_type_from_tokens(&toks)
3362    }
3363
3364    fn con(name: &str) -> Type {
3365        Type::Con {
3366            qualifier: None,
3367            name: name.into(),
3368            span: Span::default(),
3369        }
3370    }
3371
3372    fn qualified_con(qualifier: &str, name: &str) -> Type {
3373        Type::Con {
3374            qualifier: Some(qualifier.into()),
3375            name: name.into(),
3376            span: Span::default(),
3377        }
3378    }
3379
3380    fn app(head: Type, args: Vec<Type>) -> Type {
3381        Type::App(Box::new(head), args, Span::default())
3382    }
3383
3384    fn list(inner: Type) -> Type {
3385        Type::List(Box::new(inner), Span::default())
3386    }
3387
3388    fn tuple(items: Vec<Type>) -> Type {
3389        Type::Tuple(items, Span::default())
3390    }
3391
3392    fn fun(param: Type, result: Type) -> Type {
3393        Type::Fun(Box::new(param), Box::new(result), Span::default())
3394    }
3395
3396    fn var(name: &str) -> Type {
3397        Type::Var(name.into(), Span::default())
3398    }
3399
3400    fn unit() -> Type {
3401        Type::Unit(Span::default())
3402    }
3403
3404    fn constrained(body: Type) -> Type {
3405        Type::Constrained(Box::new(body), Span::default())
3406    }
3407
3408    #[test]
3409    fn atoms() {
3410        assert_eq!(ty("Party"), Some(con("Party")));
3411        assert_eq!(ty("Decimal"), Some(con("Decimal")));
3412        assert_eq!(ty("a"), Some(var("a")));
3413        assert_eq!(ty("()"), Some(unit()));
3414    }
3415
3416    #[test]
3417    fn application_vs_constructor() {
3418        // The whole point of the new model: `ContractId Foo` is an APPLICATION,
3419        // not one opaque name.
3420        assert_eq!(
3421            ty("ContractId Foo"),
3422            Some(app(con("ContractId"), vec![con("Foo")]))
3423        );
3424        assert_eq!(
3425            ty("Optional (ContractId Foo)"),
3426            Some(app(
3427                con("Optional"),
3428                vec![app(con("ContractId"), vec![con("Foo")])]
3429            ))
3430        );
3431        assert_eq!(
3432            ty("Map Text Int"),
3433            Some(app(con("Map"), vec![con("Text"), con("Int")]))
3434        );
3435    }
3436
3437    #[test]
3438    fn qualified_constructor_keeps_qualifier() {
3439        assert_eq!(
3440            ty("DA.Map.Map Text Int"),
3441            Some(app(
3442                qualified_con("DA.Map", "Map"),
3443                vec![con("Text"), con("Int")]
3444            ))
3445        );
3446    }
3447
3448    #[test]
3449    fn list_and_tuple() {
3450        assert_eq!(ty("[Text]"), Some(list(con("Text"))));
3451        assert_eq!(
3452            ty("(Int, Text)"),
3453            Some(tuple(vec![con("Int"), con("Text")]))
3454        );
3455        // A tuple is NOT a grouping paren — must stay a Tuple, never collapse.
3456        assert_eq!(
3457            ty("(a, b, c)"),
3458            Some(tuple(vec![var("a"), var("b"), var("c")]))
3459        );
3460        // Single grouping paren unwraps.
3461        assert_eq!(ty("(Text)"), Some(con("Text")));
3462    }
3463
3464    #[test]
3465    fn function_types_are_arrows_not_names() {
3466        // These are exactly the corpus strings the old matcher swallowed into
3467        // one opaque `Named`.
3468        assert_eq!(ty("Int -> Int"), Some(fun(con("Int"), con("Int"))));
3469        // Right associativity: `a -> b -> c` == `a -> (b -> c)`.
3470        assert_eq!(
3471            ty("Int -> Text -> Bool"),
3472            Some(fun(con("Int"), fun(con("Text"), con("Bool"))))
3473        );
3474        assert_eq!(
3475            ty("Party -> Script ()"),
3476            Some(fun(con("Party"), app(con("Script"), vec![unit()])))
3477        );
3478    }
3479
3480    #[test]
3481    fn script_application() {
3482        // `Script ()` ×147 in the corpus — an application flattened to `Named`
3483        // before. Now a real App.
3484        assert_eq!(ty("Script ()"), Some(app(con("Script"), vec![unit()])));
3485    }
3486
3487    #[test]
3488    fn numeric_nat_literal_is_dropped() {
3489        // `Numeric 10`: the `10` is a type-level nat, not a type, so it drops
3490        // and the head Con stands alone. `Numeric n` keeps the type variable.
3491        assert_eq!(ty("Numeric 10"), Some(con("Numeric")));
3492        assert_eq!(ty("Numeric n"), Some(app(con("Numeric"), vec![var("n")])));
3493    }
3494
3495    #[test]
3496    fn constraint_context_is_dropped_body_kept() {
3497        // `NumericScale n => Numeric 37 -> Numeric n` — a constrained function.
3498        // Context dropped; body (the arrow) kept.
3499        assert_eq!(
3500            ty("NumericScale n => Numeric 37 -> Numeric n"),
3501            Some(constrained(fun(
3502                con("Numeric"),
3503                app(con("Numeric"), vec![var("n")])
3504            )))
3505        );
3506        // Tuple context `(Eq a, Show a) => a` also drops cleanly.
3507        assert_eq!(ty("(Eq a, Show a) => a"), Some(constrained(var("a"))));
3508    }
3509
3510    #[test]
3511    fn unparseable_is_none() {
3512        // A trailing arrow with no body is not a clean type → unknown (None),
3513        // never a half-parse.
3514        assert_eq!(ty("Int ->"), None);
3515        assert_eq!(ty("-> Int"), None);
3516    }
3517
3518    #[test]
3519    fn ty_is_populated_through_real_parse() {
3520        // End-to-end: the wiring actually fills `ty` on template fields and the
3521        // choice return type, from the real token stream.
3522        let src = r#"module M where
3523template T
3524  with
3525    owner : Party
3526    held : ContractId Asset
3527  where
3528    signatory owner
3529    choice Go : Optional (ContractId Asset)
3530      controller owner
3531      do
3532        pure None
3533"#;
3534        let (m, _) = parse_module(src).into_parts();
3535        let t = match &m.decls[0] {
3536            Decl::Template(t) => t,
3537            other => panic!("expected template, got {other:?}"),
3538        };
3539        assert_eq!(t.fields[0].ty, Some(con("Party")));
3540        assert_eq!(
3541            t.fields[1].ty,
3542            Some(app(con("ContractId"), vec![con("Asset")]))
3543        );
3544        let choice = match &t
3545            .body
3546            .iter()
3547            .find(|d| matches!(d, TemplateBodyDecl::Choice(_)))
3548        {
3549            Some(TemplateBodyDecl::Choice(c)) => (*c).clone(),
3550            _ => panic!("expected choice"),
3551        };
3552        assert_eq!(
3553            choice.return_ty,
3554            Some(app(
3555                con("Optional"),
3556                vec![app(con("ContractId"), vec![con("Asset")])]
3557            ))
3558        );
3559    }
3560
3561    #[test]
3562    fn ty_is_populated_on_key_and_interface_method() {
3563        // The other two type-bearing nodes: a template `key ... : T` and an
3564        // interface method signature both fill `ty` from the token stream.
3565        let src = r#"module M where
3566template T
3567  with
3568    owner : Party
3569  where
3570    signatory owner
3571    key owner : Party
3572    maintainer owner
3573
3574interface I where
3575  getAmount : Numeric 10
3576"#;
3577        let (m, _) = parse_module(src).into_parts();
3578        let t = match &m.decls[0] {
3579            Decl::Template(t) => t,
3580            other => panic!("expected template, got {other:?}"),
3581        };
3582        let key_ty = t.body.iter().find_map(|d| match d {
3583            TemplateBodyDecl::Key { ty, .. } => Some(ty.clone()),
3584            _ => None,
3585        });
3586        assert_eq!(key_ty, Some(Some(con("Party"))));
3587
3588        let iface = match &m.decls[1] {
3589            Decl::Interface(i) => i,
3590            other => panic!("expected interface, got {other:?}"),
3591        };
3592        // `Numeric 10` — the nat literal is dropped, leaving the bare head Con.
3593        assert_eq!(iface.methods[0].ty, Some(con("Numeric")));
3594    }
3595
3596    #[test]
3597    fn malformed_guarded_equation_reports_missing_equals_and_continues() {
3598        let src = "module M where\nf x | x > 0\ng = 1\n";
3599        let (module, diagnostics) = parse_module(src).into_parts();
3600
3601        assert!(
3602            diagnostics.iter().any(
3603                |diagnostic| diagnostic.message == "expected '=' after guard"
3604                    && diagnostic.category == DiagnosticCategory::Malformed
3605            ),
3606            "expected guard diagnostic, got {diagnostics:?}"
3607        );
3608        assert!(
3609            module
3610                .decls
3611                .iter()
3612                .any(|decl| matches!(decl, Decl::Function(function) if function.name == "g")),
3613            "parser should recover to the following declaration: {:?}",
3614            module.decls
3615        );
3616    }
3617
3618    #[test]
3619    fn malformed_brackets_do_not_underflow_recovery_scans() {
3620        let src = "module M where\ntemplate T\n  with\n    owner : Party\n  where\n    key owner ) : Party\n    maintainer owner\n\nf = (]\ng = 1\n";
3621        let (module, _diagnostics) = parse_module(src).into_parts();
3622
3623        assert_eq!(module.name, "M");
3624        assert!(
3625            module
3626                .decls
3627                .iter()
3628                .any(|decl| matches!(decl, Decl::Function(function) if function.name == "g")),
3629            "parser should recover to the following declaration: {:?}",
3630            module.decls
3631        );
3632    }
3633
3634    #[test]
3635    fn headerless_file_keeps_legacy_unknown_name_fallback() {
3636        let (module, _diagnostics) = parse_module("f = 1\n").into_parts();
3637
3638        assert_eq!(module.name, "Unknown");
3639        assert!(
3640            module
3641                .decls
3642                .iter()
3643                .any(|decl| matches!(decl, Decl::Function(function) if function.name == "f")),
3644            "expected function declaration to be parsed: {:?}",
3645            module.decls
3646        );
3647    }
3648}
3649
3650#[cfg(test)]
3651mod parser_tests {
3652    use super::*;
3653
3654    fn parse(src: &str) -> (Module, Vec<ParseDiagnostic>) {
3655        parse_module(src).into_parts()
3656    }
3657
3658    fn section_side_for_fn(module: &Module, name: &str) -> SectionSide {
3659        let body = get_first_equation_body(module, name);
3660        match body {
3661            Expr::Section { side, .. } => *side,
3662            other => panic!("expected section body for {name}, got {other:?}"),
3663        }
3664    }
3665
3666    #[test]
3667    fn import_style_distinguishes_qualified_prefix_and_postfix() {
3668        let (module, diagnostics) = parse(
3669            "module M where
3670import qualified Foo.Bar as FB
3671import DA.Map qualified as Map
3672import Baz as B",
3673        );
3674
3675        assert!(diagnostics.is_empty());
3676        assert_eq!(
3677            module.imports.iter().map(|i| i.style).collect::<Vec<_>>(),
3678            vec![
3679                ImportStyle::Qualified,
3680                ImportStyle::Qualified,
3681                ImportStyle::Unqualified,
3682            ]
3683        );
3684    }
3685
3686    #[test]
3687    fn expression_sections_encode_side_in_ast() {
3688        let (module, diagnostics) = parse(
3689            "module M where
3690f = (+ 1)
3691g = (+)
3692",
3693        );
3694
3695        assert!(diagnostics.is_empty());
3696        assert!(matches!(
3697            get_first_equation_body(&module, "f"),
3698            Expr::Section {
3699                operand: Some(_),
3700                ..
3701            }
3702        ));
3703        assert!(matches!(
3704            get_first_equation_body(&module, "g"),
3705            Expr::Section { operand: None, .. }
3706        ));
3707        assert_eq!(section_side_for_fn(&module, "f"), SectionSide::Right);
3708        assert_eq!(section_side_for_fn(&module, "g"), SectionSide::Right);
3709    }
3710
3711    #[test]
3712    fn do_expr_is_allowed_for_top_level_expression_parsing() {
3713        let (module, diagnostics) = parse(
3714            "module M where
3715f = do
3716  pure True
3717",
3718        );
3719
3720        assert!(diagnostics.is_empty());
3721        assert!(matches!(
3722            get_first_equation_body(&module, "f"),
3723            Expr::Do { .. }
3724        ));
3725    }
3726
3727    #[test]
3728    fn do_expr_is_disallowed_for_case_scrutinee_parsing() {
3729        let (module, diagnostics) = parse(
3730            "module M where
3731f = case do 1 of
3732  x -> x
3733",
3734        );
3735
3736        assert!(diagnostics
3737            .iter()
3738            .any(|d| d.message == "expected 'of' in case expression"));
3739        assert!(matches!(
3740            get_first_equation_body(&module, "f"),
3741            Expr::Error { .. }
3742        ));
3743    }
3744
3745    fn get_first_equation_body<'a>(module: &'a Module, name: &str) -> &'a Expr {
3746        let function = module
3747            .decls
3748            .iter()
3749            .find_map(|d| match d {
3750                Decl::Function(f) if f.name == name => Some(f),
3751                _ => None,
3752            })
3753            .unwrap_or_else(|| panic!("missing function declaration {name}"));
3754        let first_equation = function
3755            .equations
3756            .first()
3757            .unwrap_or_else(|| panic!("missing equation for function {name}"));
3758        &first_equation.body
3759    }
3760}