Skip to main content

daml_parser/
parse.rs

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