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