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