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