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