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