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