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