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().map_or_else(
1349                || {
1350                    self.diag("interface instance missing template name after 'for'");
1351                    None
1352                },
1353                Some,
1354            )
1355        } else {
1356            None
1357        };
1358        let mut methods = Vec::new();
1359        if self.eat_keyword("where")
1360            && (self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace))
1361        {
1362            loop {
1363                while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
1364                match self.peek() {
1365                    None => break,
1366                    Some(TokenKind::VRBrace | TokenKind::RBrace) => {
1367                        self.bump();
1368                        break;
1369                    }
1370                    // Stray closer: discard so the loop always progresses.
1371                    Some(TokenKind::RParen | TokenKind::RBracket) => {
1372                        self.bump();
1373                        continue;
1374                    }
1375                    _ => {}
1376                }
1377                if let Some(b) = self.binding() {
1378                    methods.push(b);
1379                } else {
1380                    self.skip_to_item_end();
1381                }
1382            }
1383        }
1384        Some(InterfaceInstanceDecl {
1385            interface_name,
1386            for_template,
1387            methods,
1388            pos,
1389            span: self.node_span(start_i),
1390        })
1391    }
1392
1393    // ----- functions -----------------------------------------------------
1394
1395    /// A top-level item starting with a lowercase identifier: type
1396    /// signature or function equation. Operator definitions and other
1397    /// exotica return None.
1398    fn function_item(&mut self) -> Option<Decl> {
1399        let pos = self.pos();
1400        let start_i = self.i;
1401        let name = match self.peek().cloned() {
1402            Some(TokenKind::LowerId {
1403                qualifier: None,
1404                name,
1405            }) => name,
1406            _ => return None,
1407        };
1408
1409        // Type signature: `name [, name2] : Type`
1410        let mut j = self.i + 1;
1411        let mut is_sig = false;
1412        loop {
1413            match self.toks.get(j).map(|t| &t.kind) {
1414                Some(TokenKind::Comma) => {
1415                    j += 1;
1416                    if matches!(
1417                        self.toks.get(j).map(|t| &t.kind),
1418                        Some(TokenKind::LowerId {
1419                            qualifier: None,
1420                            ..
1421                        })
1422                    ) {
1423                        j += 1;
1424                        continue;
1425                    }
1426                    break;
1427                }
1428                Some(TokenKind::Op(o)) if o.as_str() == ":" => {
1429                    is_sig = true;
1430                    break;
1431                }
1432                _ => break,
1433            }
1434        }
1435        if is_sig {
1436            self.bump(); // name
1437            while self.eat(&TokenKind::Comma) {
1438                self.bump(); // more names
1439            }
1440            self.eat_op(":");
1441            let ty_start = self.i;
1442            self.skip_to_item_end();
1443            let ty = self.parse_type_annotation(ty_start, self.i, "function");
1444            return Some(Decl::Function(FunctionDecl {
1445                name,
1446                ty,
1447                equations: Vec::new(),
1448                pos,
1449                sig_span: Some(self.node_span(start_i)),
1450                span: self.node_span(start_i),
1451            }));
1452        }
1453
1454        // Function equation: name pats (= expr | guards), optional where.
1455        self.bump(); // name
1456        let mut params = Vec::new();
1457        while !self.at_op("=") && !self.at_op("|") {
1458            // Combined signature + body: `name (x : a) : RetType = expr` —
1459            // consume the return-type annotation up to the `=`.
1460            if self.at_op(":") {
1461                self.bump();
1462                let mut brackets = 0usize;
1463                while let Some(t) = self.peek() {
1464                    match t {
1465                        TokenKind::Op(o) if o.as_str() == "=" && brackets == 0 => break,
1466                        TokenKind::VSemi
1467                        | TokenKind::VRBrace
1468                        | TokenKind::Semi
1469                        | TokenKind::RBrace => break,
1470                        TokenKind::LParen | TokenKind::LBracket => brackets += 1,
1471                        TokenKind::RParen | TokenKind::RBracket => {
1472                            brackets = brackets.saturating_sub(1)
1473                        }
1474                        _ => {}
1475                    }
1476                    self.i += 1;
1477                }
1478                continue;
1479            }
1480            // Infix operator definition: `f $ x = f x`, `as <&> f = ...` —
1481            // operators have no IR surface; skip the item silently.
1482            if matches!(self.peek(), Some(TokenKind::Op(o)) if !is_reserved_op(o)) {
1483                self.skip_to_item_end();
1484                return None;
1485            }
1486            match self.peek() {
1487                None
1488                | Some(
1489                    TokenKind::VSemi | TokenKind::VRBrace | TokenKind::Semi | TokenKind::RBrace,
1490                ) => {
1491                    self.diag(format!("could not parse equation for '{name}'"));
1492                    return None;
1493                }
1494                _ => {}
1495            }
1496            match self.pattern_atom() {
1497                Some(p) => params.push(p),
1498                None => {
1499                    self.diag(format!("bad parameter pattern in '{name}'"));
1500                    return None;
1501                }
1502            }
1503        }
1504        let (body, guards) = self.equation_rhs()?;
1505        let where_bindings = if self.eat_keyword("where") {
1506            self.binding_block()
1507        } else {
1508            Vec::new()
1509        };
1510        self.skip_to_item_end();
1511        Some(Decl::Function(FunctionDecl {
1512            name,
1513            ty: None,
1514            equations: vec![Equation {
1515                params,
1516                body,
1517                guards,
1518                where_bindings,
1519                pos,
1520                span: self.node_span(start_i),
1521            }],
1522            pos,
1523            sig_span: None,
1524            span: self.node_span(start_i),
1525        }))
1526    }
1527
1528    /// `= expr` or `| guard = expr | guard = expr ...`
1529    fn equation_rhs(&mut self) -> Option<(Expr, Vec<(Expr, Expr)>)> {
1530        if self.eat_op("=") {
1531            return Some((self.expr(), Vec::new()));
1532        }
1533        let mut guards = Vec::new();
1534        while self.eat_op("|") {
1535            // Comma-separated guard qualifiers, each a boolean expression
1536            // or a pattern guard `pat <- expr`.
1537            let g = loop {
1538                let g = self.expr();
1539                if self.eat_op("<-") {
1540                    let _ = self.expr(); // pattern guard: keep the pattern side
1541                }
1542                if !self.eat(&TokenKind::Comma) {
1543                    break g;
1544                }
1545            };
1546            if !self.eat_op("=") {
1547                self.diag("expected '=' after guard");
1548                return None;
1549            }
1550            let e = self.expr();
1551            guards.push((g, e));
1552        }
1553        if guards.is_empty() {
1554            self.diag("expected '=' or guarded right-hand side in equation");
1555            None
1556        } else {
1557            let first = guards[0].1.clone();
1558            Some((first, guards))
1559        }
1560    }
1561
1562    /// `{ binding ; binding ; ... }` for let/where blocks.
1563    fn binding_block(&mut self) -> Vec<Binding> {
1564        let mut bindings = Vec::new();
1565        if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
1566            return bindings;
1567        }
1568        loop {
1569            while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
1570            match self.peek() {
1571                None => break,
1572                Some(TokenKind::VRBrace | TokenKind::RBrace) => {
1573                    self.bump();
1574                    break;
1575                }
1576                // A stray closing bracket inside a block is garbage from a
1577                // failed item parse — discard it or the loop cannot make
1578                // progress (skip_to_item_end deliberately stops before
1579                // unmatched closers).
1580                Some(TokenKind::RParen | TokenKind::RBracket) => {
1581                    self.bump();
1582                    continue;
1583                }
1584                _ => {}
1585            }
1586            match self.binding() {
1587                Some(b) => bindings.push(b),
1588                None => self.skip_to_item_end(),
1589            }
1590        }
1591        bindings
1592    }
1593
1594    /// One binding: `pat = expr`, `f x y = expr`, guarded variants, or a
1595    /// type signature (skipped, returns None).
1596    fn binding(&mut self) -> Option<Binding> {
1597        let pos = self.pos();
1598        let start_i = self.i;
1599        // Operator binding or signature: `(==) : Text -> Bool = ...` —
1600        // skip the whole item; operators aren't surfaced in the IR.
1601        if self.at(&TokenKind::LParen)
1602            && matches!(self.peek_at(1), Some(TokenKind::Op(_)))
1603            && self.peek_at(2) == Some(&TokenKind::RParen)
1604        {
1605            self.skip_to_item_end();
1606            return None;
1607        }
1608        let pat = self.pattern_atom()?;
1609        let mut params = Vec::new();
1610        loop {
1611            if self.at_op("=") {
1612                self.bump();
1613                let expr = self.expr();
1614                // Bindings can carry their own where blocks.
1615                if self.eat_keyword("where") {
1616                    let _ = self.binding_block();
1617                }
1618                return Some(Binding {
1619                    pat,
1620                    params,
1621                    expr,
1622                    pos,
1623                    span: self.node_span(start_i),
1624                });
1625            }
1626            if self.at_op("|") {
1627                let (body, _) = self.equation_rhs()?;
1628                if self.eat_keyword("where") {
1629                    let _ = self.binding_block();
1630                }
1631                return Some(Binding {
1632                    pat,
1633                    params,
1634                    expr: body,
1635                    pos,
1636                    span: self.node_span(start_i),
1637                });
1638            }
1639            if self.at_op(":") {
1640                if params.is_empty() {
1641                    // Type signature inside a let/where block — skip it.
1642                    self.skip_to_item_end();
1643                    return None;
1644                }
1645                // Combined signature + body: `f (x : a) : Ret = expr` —
1646                // consume the return-type annotation up to the `=`.
1647                self.bump();
1648                let mut brackets = 0usize;
1649                while let Some(t) = self.peek() {
1650                    match t {
1651                        TokenKind::Op(o) if o.as_str() == "=" && brackets == 0 => break,
1652                        TokenKind::VSemi
1653                        | TokenKind::VRBrace
1654                        | TokenKind::Semi
1655                        | TokenKind::RBrace => break,
1656                        TokenKind::LParen | TokenKind::LBracket => brackets += 1,
1657                        TokenKind::RParen | TokenKind::RBracket => {
1658                            brackets = brackets.saturating_sub(1)
1659                        }
1660                        _ => {}
1661                    }
1662                    self.i += 1;
1663                }
1664                continue;
1665            }
1666            // Infix operator binding with a pattern operand:
1667            // `None <?> s = ...` in a where/let block.
1668            if matches!(self.peek(), Some(TokenKind::Op(o)) if !is_reserved_op(o)) {
1669                self.skip_to_item_end();
1670                return None;
1671            }
1672            match self.peek() {
1673                None
1674                | Some(
1675                    TokenKind::VSemi | TokenKind::VRBrace | TokenKind::Semi | TokenKind::RBrace,
1676                ) => return None,
1677                _ => {}
1678            }
1679            params.push(self.pattern_atom()?);
1680        }
1681    }
1682
1683    // ----- patterns ------------------------------------------------------
1684
1685    fn pattern_atom(&mut self) -> Option<Pat> {
1686        if self.depth >= MAX_RECURSION_DEPTH {
1687            return None;
1688        }
1689        self.depth += 1;
1690        let result = self.pattern_atom_inner();
1691        self.depth -= 1;
1692        result
1693    }
1694
1695    fn pattern_atom_inner(&mut self) -> Option<Pat> {
1696        let pos = self.pos();
1697        let start_i = self.i;
1698        // Lazy / strict pattern markers: `~(as, bs)`, `!x`.
1699        if self.at_op("~") || self.at_op("!") {
1700            self.bump();
1701            return self.pattern_atom();
1702        }
1703        match self.peek().cloned() {
1704            Some(TokenKind::LowerId {
1705                qualifier: None,
1706                name,
1707            }) => {
1708                self.bump();
1709                if name == "_" {
1710                    return Some(Pat::Wild {
1711                        pos,
1712                        span: self.node_span(start_i),
1713                    });
1714                }
1715                if self.at_op("@") {
1716                    self.bump();
1717                    let inner = self.pattern_atom()?;
1718                    return Some(Pat::As {
1719                        name,
1720                        pat: Box::new(inner),
1721                        pos,
1722                        span: self.node_span(start_i),
1723                    });
1724                }
1725                Some(Pat::Var {
1726                    name,
1727                    pos,
1728                    span: self.node_span(start_i),
1729                })
1730            }
1731            Some(TokenKind::Op(o)) if o.as_str() == "_" => {
1732                self.bump();
1733                Some(Pat::Wild {
1734                    pos,
1735                    span: self.node_span(start_i),
1736                })
1737            }
1738            Some(TokenKind::UpperId { qualifier, name }) => {
1739                self.bump();
1740                // Record pattern `Foo {..}` / `Foo {x = y}` /
1741                // `Foo with claim; tag`.
1742                if self.at(&TokenKind::LBrace) {
1743                    self.skip_balanced_braces();
1744                } else if self.eat_keyword("with") {
1745                    let _ = self.record_fields();
1746                }
1747                Some(Pat::Con {
1748                    qualifier,
1749                    name,
1750                    args: Vec::new(),
1751                    pos,
1752                    span: self.node_span(start_i),
1753                })
1754            }
1755            Some(TokenKind::IntLit(text)) => {
1756                self.bump();
1757                Some(Pat::Lit {
1758                    kind: LitKind::Int,
1759                    text,
1760                    pos,
1761                    span: self.node_span(start_i),
1762                })
1763            }
1764            Some(TokenKind::DecimalLit(text)) => {
1765                self.bump();
1766                Some(Pat::Lit {
1767                    kind: LitKind::Decimal,
1768                    text,
1769                    pos,
1770                    span: self.node_span(start_i),
1771                })
1772            }
1773            Some(TokenKind::StringLit(text)) => {
1774                self.bump();
1775                Some(Pat::Lit {
1776                    kind: LitKind::Text,
1777                    text,
1778                    pos,
1779                    span: self.node_span(start_i),
1780                })
1781            }
1782            Some(TokenKind::CharLit(text)) => {
1783                self.bump();
1784                Some(Pat::Lit {
1785                    kind: LitKind::Char,
1786                    text,
1787                    pos,
1788                    span: self.node_span(start_i),
1789                })
1790            }
1791            Some(TokenKind::LParen) => {
1792                self.bump();
1793                if self.eat(&TokenKind::RParen) {
1794                    return Some(Pat::Con {
1795                        qualifier: None,
1796                        name: "()".into(),
1797                        args: Vec::new(),
1798                        pos,
1799                        span: self.node_span(start_i),
1800                    });
1801                }
1802                // View pattern `(expr -> pat)`: scan for a top-level `->`
1803                // inside these parens; the expression side is discarded and
1804                // the pattern after the arrow is the binding. A top-level
1805                // `:` before the arrow means the arrow belongs to a type
1806                // annotation (`(f : Int -> Bool)`), not a view pattern.
1807                {
1808                    let mut depth = 0usize;
1809                    let mut j = self.i;
1810                    let mut arrow = None;
1811                    while let Some(t) = self.toks.get(j).map(|t| &t.kind) {
1812                        match t {
1813                            TokenKind::LParen | TokenKind::LBracket => depth += 1,
1814                            TokenKind::RParen | TokenKind::RBracket => {
1815                                if depth == 0 {
1816                                    break;
1817                                }
1818                                depth -= 1;
1819                            }
1820                            TokenKind::Op(o) if o.as_str() == ":" && depth == 0 => break,
1821                            TokenKind::Op(o) if o.as_str() == "->" && depth == 0 => {
1822                                arrow = Some(j);
1823                                break;
1824                            }
1825                            TokenKind::VSemi | TokenKind::VRBrace => break,
1826                            // A lambda's arrow belongs to the lambda.
1827                            TokenKind::Op(o) if o.as_str() == "\\" => break,
1828                            _ => {}
1829                        }
1830                        j += 1;
1831                    }
1832                    if let Some(j) = arrow {
1833                        self.i = j + 1; // skip the view expression and `->`
1834                        let inner = self.pattern()?;
1835                        self.eat(&TokenKind::RParen);
1836                        return Some(inner);
1837                    }
1838                }
1839                let first = self.pattern()?;
1840                // Type-annotated pattern `(e : AnyException)`: skip the type.
1841                if self.at_op(":") {
1842                    let mut depth = 0usize;
1843                    while let Some(t) = self.peek() {
1844                        match t {
1845                            TokenKind::LParen | TokenKind::LBracket => depth += 1,
1846                            TokenKind::RParen if depth == 0 => break,
1847                            TokenKind::RParen | TokenKind::RBracket => {
1848                                depth = depth.saturating_sub(1)
1849                            }
1850                            TokenKind::VSemi | TokenKind::VRBrace => break,
1851                            _ => {}
1852                        }
1853                        self.i += 1;
1854                    }
1855                }
1856                if self.at(&TokenKind::Comma) {
1857                    let mut items = vec![first];
1858                    while self.eat(&TokenKind::Comma) {
1859                        items.push(self.pattern()?);
1860                    }
1861                    self.eat(&TokenKind::RParen);
1862                    return Some(Pat::Tuple {
1863                        items,
1864                        pos,
1865                        span: self.node_span(start_i),
1866                    });
1867                }
1868                self.eat(&TokenKind::RParen);
1869                Some(first)
1870            }
1871            Some(TokenKind::LBracket) => {
1872                self.bump();
1873                let mut items = Vec::new();
1874                if !self.eat(&TokenKind::RBracket) {
1875                    loop {
1876                        items.push(self.pattern()?);
1877                        if !self.eat(&TokenKind::Comma) {
1878                            break;
1879                        }
1880                    }
1881                    self.eat(&TokenKind::RBracket);
1882                }
1883                Some(Pat::List {
1884                    items,
1885                    pos,
1886                    span: self.node_span(start_i),
1887                })
1888            }
1889            _ => None,
1890        }
1891    }
1892
1893    /// Full pattern: constructor applications and infix cons `x :: xs`.
1894    fn pattern(&mut self) -> Option<Pat> {
1895        if self.depth >= MAX_RECURSION_DEPTH {
1896            return None;
1897        }
1898        self.depth += 1;
1899        let result = self.pattern_inner();
1900        self.depth -= 1;
1901        result
1902    }
1903
1904    fn pattern_inner(&mut self) -> Option<Pat> {
1905        let pos = self.pos();
1906        let start_i = self.i;
1907        let first = match self.peek().cloned() {
1908            Some(TokenKind::UpperId { qualifier, name }) => {
1909                self.bump();
1910                if self.at(&TokenKind::LBrace) || self.at_keyword("with") {
1911                    if self.eat_keyword("with") {
1912                        let _ = self.record_fields();
1913                    } else {
1914                        self.skip_balanced_braces();
1915                    }
1916                    Pat::Con {
1917                        qualifier,
1918                        name,
1919                        args: Vec::new(),
1920                        pos,
1921                        span: self.node_span(start_i),
1922                    }
1923                } else {
1924                    let mut args = Vec::new();
1925                    while let Some(a) = self.try_pattern_atom() {
1926                        args.push(a);
1927                    }
1928                    Pat::Con {
1929                        qualifier,
1930                        name,
1931                        args,
1932                        pos,
1933                        span: self.node_span(start_i),
1934                    }
1935                }
1936            }
1937            _ => self.pattern_atom()?,
1938        };
1939        if self.at_op("::") {
1940            self.bump();
1941            let rest = self.pattern()?;
1942            return Some(Pat::Con {
1943                qualifier: None,
1944                name: "::".into(),
1945                args: vec![first, rest],
1946                pos,
1947                span: self.node_span(start_i),
1948            });
1949        }
1950        Some(first)
1951    }
1952
1953    fn try_pattern_atom(&mut self) -> Option<Pat> {
1954        match self.peek() {
1955            Some(
1956                TokenKind::LowerId {
1957                    qualifier: None, ..
1958                }
1959                | TokenKind::UpperId { .. }
1960                | TokenKind::IntLit(_)
1961                | TokenKind::DecimalLit(_)
1962                | TokenKind::StringLit(_)
1963                | TokenKind::CharLit(_)
1964                | TokenKind::LParen
1965                | TokenKind::LBracket,
1966            ) => self.pattern_atom(),
1967            _ => None,
1968        }
1969    }
1970
1971    fn skip_balanced_braces(&mut self) {
1972        let mut depth = 0usize;
1973        while let Some(t) = self.peek() {
1974            match t {
1975                TokenKind::LBrace => depth += 1,
1976                TokenKind::RBrace => {
1977                    if depth == 0 {
1978                        return;
1979                    }
1980                    depth -= 1;
1981                    if depth == 0 {
1982                        self.i += 1;
1983                        return;
1984                    }
1985                }
1986                _ => {}
1987            }
1988            self.i += 1;
1989        }
1990    }
1991
1992    // ----- expressions ---------------------------------------------------
1993
1994    fn expr(&mut self) -> Expr {
1995        self.expr_prec(0, DoExpressionMode::Allow)
1996    }
1997
1998    fn expr_no_do(&mut self) -> Expr {
1999        self.expr_prec(0, DoExpressionMode::Disallow)
2000    }
2001
2002    /// Comma-separated expressions (signatory/observer/controller lists).
2003    fn expr_comma_list(&mut self) -> Vec<Expr> {
2004        let mut out = vec![self.expr()];
2005        while self.eat(&TokenKind::Comma) {
2006            out.push(self.expr());
2007        }
2008        out
2009    }
2010
2011    fn expr_comma_list_no_do(&mut self) -> Vec<Expr> {
2012        let mut out = vec![self.expr_no_do()];
2013        while self.eat(&TokenKind::Comma) {
2014            out.push(self.expr_no_do());
2015        }
2016        out
2017    }
2018
2019    fn expr_prec(&mut self, min_prec: u8, do_mode: DoExpressionMode) -> Expr {
2020        let pos = self.pos();
2021        let start_i = self.i;
2022        if self.depth >= MAX_RECURSION_DEPTH {
2023            // Hostile nesting: degrade to raw text instead of recursing, and
2024            // report it so the degraded region is not silently mistaken for
2025            // unsupported syntax. `skip_to_item_end` below consumes the rest of
2026            // the item, so this trips about once per affected declaration.
2027            self.diag_cat(
2028                DiagnosticCategory::RecursionLimit,
2029                "expression nesting too deep; truncated to raw text",
2030            );
2031            let start = self.i;
2032            self.skip_to_item_end();
2033            if self.i == start {
2034                self.bump();
2035            }
2036            return Expr::Error {
2037                raw: self.slice_text(start),
2038                span: self.node_span(start),
2039                pos,
2040            };
2041        }
2042        self.depth += 1;
2043        let result = self.expr_prec_inner(min_prec, do_mode, pos, start_i);
2044        self.depth -= 1;
2045        result
2046    }
2047
2048    fn expr_prec_inner(
2049        &mut self,
2050        min_prec: u8,
2051        do_mode: DoExpressionMode,
2052        pos: Pos,
2053        start_i: usize,
2054    ) -> Expr {
2055        let mut lhs = match self.unary(do_mode) {
2056            Some(e) => e,
2057            None => {
2058                // Unparseable here: degrade to raw text up to the item end.
2059                let start = self.i;
2060                self.skip_to_item_end();
2061                if self.i == start {
2062                    self.bump();
2063                }
2064                return Expr::Error {
2065                    raw: self.slice_text(start),
2066                    span: self.node_span(start),
2067                    pos,
2068                };
2069            }
2070        };
2071        loop {
2072            let (op, prec, right_assoc) = match self.peek() {
2073                Some(TokenKind::Op(o)) => {
2074                    let o = o.clone();
2075                    if is_reserved_op(&o) {
2076                        // `e : Type` annotation: consume the type, keep e.
2077                        if o == ":" {
2078                            self.bump();
2079                            self.skip_type_tokens();
2080                            continue;
2081                        }
2082                        break;
2083                    }
2084                    let (p, r) = fixity(&o);
2085                    (o, p, r)
2086                }
2087                Some(TokenKind::Backtick) => {
2088                    // `e `div` e` — infix function application.
2089                    let name = match self.peek_at(1) {
2090                        Some(
2091                            TokenKind::LowerId { qualifier, name }
2092                            | TokenKind::UpperId { qualifier, name },
2093                        ) => qualifier
2094                            .as_ref()
2095                            .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
2096                        _ => break,
2097                    };
2098                    if self.peek_at(2) != Some(&TokenKind::Backtick) {
2099                        break;
2100                    }
2101                    (format!("`{name}`").into(), 9, false)
2102                }
2103                _ => break,
2104            };
2105            if prec < min_prec {
2106                break;
2107            }
2108            self.bump();
2109            if op.starts_with('`') {
2110                self.bump();
2111                self.bump();
2112            }
2113            let next_min = if right_assoc { prec } else { prec + 1 };
2114            let rhs = self.expr_prec(next_min, do_mode);
2115            lhs = Expr::BinOp {
2116                op,
2117                lhs: Box::new(lhs),
2118                rhs: Box::new(rhs),
2119                pos,
2120                span: self.node_span(start_i),
2121            };
2122        }
2123        lhs
2124    }
2125
2126    fn unary(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
2127        let pos = self.pos();
2128        let start_i = self.i;
2129        if self.at_op("-") {
2130            self.bump();
2131            let e = self.unary(do_mode)?;
2132            return Some(Expr::Neg {
2133                expr: Box::new(e),
2134                pos,
2135                span: self.node_span(start_i),
2136            });
2137        }
2138        self.application(do_mode)
2139    }
2140
2141    fn application(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
2142        let pos = self.pos();
2143        let start_i = self.i;
2144        let head0 = self.atom(do_mode)?;
2145        let mut head = self.projection_tail(head0);
2146        let mut args = Vec::new();
2147        loop {
2148            // Record syntax binds tighter than application:
2149            // `create Foo with x = 1` applies create to (Foo with {x = 1}).
2150            if self.at_keyword("with") {
2151                let target = args.pop().unwrap_or_else(|| {
2152                    std::mem::replace(
2153                        &mut head,
2154                        Expr::Error {
2155                            raw: String::new(),
2156                            pos,
2157                            span: Span::default(),
2158                        },
2159                    )
2160                });
2161                self.bump(); // with
2162                let fields = self.record_fields();
2163                let tpos = target.pos();
2164                let sp = Span::new(target.span().start, self.end_byte());
2165                let rec = Expr::Record {
2166                    base: Box::new(target),
2167                    fields,
2168                    pos: tpos,
2169                    span: sp,
2170                };
2171                if matches!(head, Expr::Error { ref raw, .. } if raw.is_empty()) {
2172                    head = rec;
2173                } else {
2174                    args.push(rec);
2175                }
2176                continue;
2177            }
2178            if !do_mode.allows_do() && self.at_keyword("do") {
2179                break;
2180            }
2181            // Type application `f @Type x` — consume and drop the type atom.
2182            if self.at_op("@") {
2183                self.bump();
2184                match self.peek() {
2185                    Some(TokenKind::UpperId { .. } | TokenKind::LowerId { .. }) => {
2186                        self.bump();
2187                    }
2188                    Some(TokenKind::LParen) => self.skip_balanced_parens(),
2189                    Some(TokenKind::LBracket) => {
2190                        let mut depth = 0usize;
2191                        while let Some(t) = self.peek() {
2192                            match t {
2193                                TokenKind::LBracket => depth += 1,
2194                                TokenKind::RBracket => {
2195                                    if depth == 0 {
2196                                        break;
2197                                    }
2198                                    depth -= 1;
2199                                    if depth == 0 {
2200                                        self.i += 1;
2201                                        break;
2202                                    }
2203                                }
2204                                _ => {}
2205                            }
2206                            self.i += 1;
2207                        }
2208                    }
2209                    _ => {}
2210                }
2211                continue;
2212            }
2213            match self.try_atom(do_mode) {
2214                Some(a) => args.push(self.projection_tail(a)),
2215                None => break,
2216            }
2217        }
2218        if args.is_empty() {
2219            Some(head)
2220        } else {
2221            Some(Expr::App {
2222                func: Box::new(head),
2223                args,
2224                pos,
2225                span: self.node_span(start_i),
2226            })
2227        }
2228    }
2229
2230    /// `{ f = e ; g ; .. }` after `with` (virtual block) or explicit braces.
2231    fn record_fields(&mut self) -> Vec<FieldAssign> {
2232        let mut fields = Vec::new();
2233        let explicit = self.at(&TokenKind::LBrace);
2234        if !(self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace)) {
2235            return fields;
2236        }
2237        loop {
2238            while self.eat(&TokenKind::VSemi)
2239                || self.eat(&TokenKind::Semi)
2240                || self.eat(&TokenKind::Comma)
2241            {}
2242            match self.peek() {
2243                None => break,
2244                Some(TokenKind::VRBrace) if !explicit => {
2245                    self.bump();
2246                    break;
2247                }
2248                Some(TokenKind::RBrace) => {
2249                    self.bump();
2250                    break;
2251                }
2252                // Stray closer: discard so the loop always progresses.
2253                Some(TokenKind::RParen | TokenKind::RBracket) => {
2254                    self.bump();
2255                    continue;
2256                }
2257                _ => {}
2258            }
2259            let pos = self.pos();
2260            let start_i = self.i;
2261            if self.at_op("..") {
2262                self.bump();
2263                fields.push(FieldAssign {
2264                    name: "..".into(),
2265                    value: None,
2266                    pos,
2267                    span: self.node_span(start_i),
2268                });
2269                continue;
2270            }
2271            let name = match self.peek().cloned() {
2272                Some(TokenKind::LowerId {
2273                    qualifier: None,
2274                    name,
2275                }) => {
2276                    self.bump();
2277                    name
2278                }
2279                _ => {
2280                    self.skip_to_item_end();
2281                    continue;
2282                }
2283            };
2284            if self.eat_op("=") {
2285                let value = self.expr_prec(1, DoExpressionMode::Allow);
2286                fields.push(FieldAssign {
2287                    name,
2288                    value: Some(value),
2289                    pos,
2290                    span: self.node_span(start_i),
2291                });
2292            } else {
2293                // Pun: `Foo with owner`.
2294                fields.push(FieldAssign {
2295                    name,
2296                    value: None,
2297                    pos,
2298                    span: self.node_span(start_i),
2299                });
2300            }
2301        }
2302        fields
2303    }
2304
2305    fn try_atom(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
2306        match self.peek() {
2307            Some(TokenKind::LowerId { .. }) => {
2308                let kw = self.peek().and_then(|t| t.keyword());
2309                match kw {
2310                    // Block argument: `script do ...`, `submit p do ...`.
2311                    Some("do") if do_mode.allows_do() => self.atom(do_mode),
2312                    // Keywords that begin expressions are fine as atoms in
2313                    // head position but must not be slurped as arguments.
2314                    Some(
2315                        "if" | "case" | "do" | "let" | "try" | "where" | "then" | "else" | "of"
2316                        | "in" | "controller" | "with" | "catch",
2317                    ) => None,
2318                    _ => self.atom(do_mode),
2319                }
2320            }
2321            Some(
2322                TokenKind::UpperId { .. }
2323                | TokenKind::IntLit(_)
2324                | TokenKind::DecimalLit(_)
2325                | TokenKind::StringLit(_)
2326                | TokenKind::CharLit(_)
2327                | TokenKind::LParen
2328                | TokenKind::LBracket,
2329            ) => self.atom(do_mode),
2330            // Bare trailing lambda argument: `forA xs \x -> ...`.
2331            Some(TokenKind::Op(o)) if o.as_str() == "\\" => self.atom(do_mode),
2332            _ => None,
2333        }
2334    }
2335
2336    /// Fold tight (whitespace-free) `.field` record projections onto `base`.
2337    /// Projection binds tighter than application, so `length this.note` is
2338    /// `length (this.note)` not `(length this).note`, and a chain `a.b.c`
2339    /// left-nests. A *spaced* dot (`f . g`, composition) is not tight and is
2340    /// left to the binary-operator layer untouched. Qualified names
2341    /// (`Map.lookup`) are already a single token and never reach here.
2342    fn projection_tail(&mut self, mut base: Expr) -> Expr {
2343        while self.at_tight_projection() {
2344            let start = base.span().start;
2345            let pos = base.pos();
2346            self.bump(); // '.'
2347            let Some(field_tok) = self.bump() else {
2348                self.diag("expected projection field after '.'");
2349                return base;
2350            };
2351            let TokenKind::LowerId { qualifier, name } = field_tok.kind else {
2352                self.diag("expected projection field after '.'");
2353                return base;
2354            };
2355            let field = Expr::Var {
2356                qualifier,
2357                name,
2358                pos: field_tok.pos,
2359                span: Span::new(field_tok.start, field_tok.end),
2360            };
2361            base = Expr::BinOp {
2362                op: ".".into(),
2363                lhs: Box::new(base),
2364                rhs: Box::new(field),
2365                pos,
2366                span: Span::new(start, self.end_byte()),
2367            };
2368        }
2369        base
2370    }
2371
2372    /// True when the cursor sits on a `.` that abuts a real token on its left
2373    /// and an unqualified lowercase field on its right with no whitespace on
2374    /// either side — i.e. a record projection, not function composition.
2375    fn at_tight_projection(&self) -> bool {
2376        if self.i == 0 {
2377            return false;
2378        }
2379        let dot = match self.toks.get(self.i) {
2380            Some(t) => t,
2381            None => return false,
2382        };
2383        if !matches!(&dot.kind, TokenKind::Op(o) if o.as_str() == ".") {
2384            return false;
2385        }
2386        // Tight on the left: the dot abuts the base's last byte. The previous
2387        // token must be real — a virtual layout token here means a newline or
2388        // dedent sat between base and dot, which can never be a tight dot.
2389        let prev = &self.toks[self.i - 1];
2390        if prev.is_virtual() || prev.end != dot.start {
2391            return false;
2392        }
2393        // Tight on the right: an unqualified lowercase field abuts the dot.
2394        self.toks.get(self.i + 1).is_some_and(|t| {
2395            matches!(
2396                &t.kind,
2397                TokenKind::LowerId {
2398                    qualifier: None,
2399                    ..
2400                }
2401            ) && t.start == dot.end
2402        })
2403    }
2404
2405    fn atom(&mut self, do_mode: DoExpressionMode) -> Option<Expr> {
2406        let pos = self.pos();
2407        let start_i = self.i;
2408        match self.peek().cloned() {
2409            Some(TokenKind::LowerId { qualifier, name }) => {
2410                match name.as_str() {
2411                    "if" if qualifier.is_none() => return self.if_expr(),
2412                    "case" if qualifier.is_none() => return self.case_expr(),
2413                    "do" if qualifier.is_none() => {
2414                        if !do_mode.allows_do() {
2415                            return None;
2416                        }
2417                        return self.do_expr();
2418                    }
2419                    "let" if qualifier.is_none() => return self.let_expr(),
2420                    "try" if qualifier.is_none() => return self.try_expr(),
2421                    _ => {}
2422                }
2423                self.bump();
2424                Some(Expr::Var {
2425                    qualifier,
2426                    name,
2427                    pos,
2428                    span: self.node_span(start_i),
2429                })
2430            }
2431            Some(TokenKind::UpperId { qualifier, name }) => {
2432                self.bump();
2433                let base = Expr::Con {
2434                    qualifier,
2435                    name,
2436                    pos,
2437                    span: self.node_span(start_i),
2438                };
2439                // Explicit-brace record syntax: `Foo {x = 1}`.
2440                if self.at(&TokenKind::LBrace) {
2441                    let fields = self.record_fields();
2442                    return Some(Expr::Record {
2443                        base: Box::new(base),
2444                        fields,
2445                        pos,
2446                        span: self.node_span(start_i),
2447                    });
2448                }
2449                Some(base)
2450            }
2451            Some(TokenKind::IntLit(text)) => {
2452                self.bump();
2453                Some(Expr::Lit {
2454                    kind: LitKind::Int,
2455                    text,
2456                    pos,
2457                    span: self.node_span(start_i),
2458                })
2459            }
2460            Some(TokenKind::DecimalLit(text)) => {
2461                self.bump();
2462                Some(Expr::Lit {
2463                    kind: LitKind::Decimal,
2464                    text,
2465                    pos,
2466                    span: self.node_span(start_i),
2467                })
2468            }
2469            Some(TokenKind::StringLit(text)) => {
2470                self.bump();
2471                Some(Expr::Lit {
2472                    kind: LitKind::Text,
2473                    text,
2474                    pos,
2475                    span: self.node_span(start_i),
2476                })
2477            }
2478            Some(TokenKind::CharLit(text)) => {
2479                self.bump();
2480                Some(Expr::Lit {
2481                    kind: LitKind::Char,
2482                    text,
2483                    pos,
2484                    span: self.node_span(start_i),
2485                })
2486            }
2487            Some(TokenKind::Op(o)) if o.as_str() == "\\" => self.lambda_expr(),
2488            Some(TokenKind::LParen) => self.paren_expr(),
2489            Some(TokenKind::LBracket) => self.list_expr(),
2490            _ => None,
2491        }
2492    }
2493
2494    fn if_expr(&mut self) -> Option<Expr> {
2495        let pos = self.pos();
2496        let start_i = self.i;
2497        self.bump(); // if
2498        let cond = self.expr();
2499        self.eat(&TokenKind::VSemi); // DoAndIfThenElse style
2500        if !self.eat_keyword("then") {
2501            self.diag("expected 'then'");
2502            return Some(Expr::Error {
2503                raw: format!("if {}", cond.render()),
2504                pos,
2505                span: self.node_span(start_i),
2506            });
2507        }
2508        let then_branch = self.expr();
2509        self.eat(&TokenKind::VSemi);
2510        if !self.eat_keyword("else") {
2511            self.diag("expected 'else'");
2512            return Some(Expr::Error {
2513                raw: format!("if {} then {}", cond.render(), then_branch.render()),
2514                pos,
2515                span: self.node_span(start_i),
2516            });
2517        }
2518        let else_branch = self.expr();
2519        Some(Expr::If {
2520            cond: Box::new(cond),
2521            then_branch: Box::new(then_branch),
2522            else_branch: Box::new(else_branch),
2523            pos,
2524            span: self.node_span(start_i),
2525        })
2526    }
2527
2528    fn case_expr(&mut self) -> Option<Expr> {
2529        let pos = self.pos();
2530        let start_i = self.i;
2531        self.bump(); // case
2532        let scrutinee = self.expr_no_do();
2533        if !self.eat_keyword("of") {
2534            self.diag("expected 'of' in case expression");
2535            return Some(Expr::Error {
2536                raw: format!("case {}", scrutinee.render()),
2537                pos,
2538                span: self.node_span(start_i),
2539            });
2540        }
2541        let mut alts = Vec::new();
2542        if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
2543            loop {
2544                while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
2545                match self.peek() {
2546                    None => break,
2547                    Some(TokenKind::VRBrace | TokenKind::RBrace) => {
2548                        self.bump();
2549                        break;
2550                    }
2551                    // Stray closer: discard so the loop always progresses.
2552                    Some(TokenKind::RParen | TokenKind::RBracket) => {
2553                        self.bump();
2554                        continue;
2555                    }
2556                    _ => {}
2557                }
2558                // An alternative can carry a `where` block for its body.
2559                if self.eat_keyword("where") {
2560                    let _ = self.binding_block();
2561                    continue;
2562                }
2563                match self.case_alt() {
2564                    Some(a) => alts.push(a),
2565                    None => self.skip_to_item_end(),
2566                }
2567            }
2568        }
2569        Some(Expr::Case {
2570            scrutinee: Box::new(scrutinee),
2571            alts,
2572            pos,
2573            span: self.node_span(start_i),
2574        })
2575    }
2576
2577    fn case_alt(&mut self) -> Option<Alt> {
2578        let pos = self.pos();
2579        let start_i = self.i;
2580        let pat = self.pattern()?;
2581        if self.at_op("|") {
2582            // Guarded alternative(s): take the first body, consume all.
2583            // Each guard is comma-separated qualifiers, each a boolean
2584            // expression or a pattern guard `pat <- expr`.
2585            let mut first: Option<Expr> = None;
2586            while self.eat_op("|") {
2587                loop {
2588                    let _guard = self.expr();
2589                    if self.eat_op("<-") {
2590                        let _ = self.expr();
2591                    }
2592                    if !self.eat(&TokenKind::Comma) {
2593                        break;
2594                    }
2595                }
2596                if !self.eat_op("->") {
2597                    self.diag("expected '->' in guarded case alternative");
2598                    return None;
2599                }
2600                let body = self.expr();
2601                if first.is_none() {
2602                    first = Some(body);
2603                }
2604            }
2605            return Some(Alt {
2606                pat,
2607                body: first?,
2608                pos,
2609                span: self.node_span(start_i),
2610            });
2611        }
2612        if !self.eat_op("->") {
2613            self.diag("expected '->' in case alternative");
2614            return None;
2615        }
2616        let body = self.expr();
2617        Some(Alt {
2618            pat,
2619            body,
2620            pos,
2621            span: self.node_span(start_i),
2622        })
2623    }
2624
2625    fn do_expr(&mut self) -> Option<Expr> {
2626        let pos = self.pos();
2627        let start_i = self.i;
2628        self.bump(); // do
2629        let mut stmts = Vec::new();
2630        if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
2631            loop {
2632                while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
2633                match self.peek() {
2634                    None => break,
2635                    Some(TokenKind::VRBrace | TokenKind::RBrace) => {
2636                        self.bump();
2637                        break;
2638                    }
2639                    // Stray closer: discard so the loop always progresses.
2640                    Some(TokenKind::RParen | TokenKind::RBracket) => {
2641                        self.bump();
2642                        continue;
2643                    }
2644                    _ => {}
2645                }
2646                stmts.push(self.do_stmt());
2647            }
2648        }
2649        Some(Expr::Do {
2650            stmts,
2651            pos,
2652            span: self.node_span(start_i),
2653        })
2654    }
2655
2656    fn do_stmt(&mut self) -> DoStmt {
2657        let pos = self.pos();
2658        let start_i = self.i;
2659        if self.at_keyword("let") {
2660            self.bump();
2661            let bindings = self.binding_block();
2662            // `let ... in body` as a statement is an expression.
2663            if self.eat_keyword("in") {
2664                let body = self.expr();
2665                return DoStmt::Expr {
2666                    expr: Expr::LetIn {
2667                        bindings,
2668                        body: Box::new(body),
2669                        pos,
2670                        span: self.node_span(start_i),
2671                    },
2672                    pos,
2673                    span: self.node_span(start_i),
2674                };
2675            }
2676            return DoStmt::Let {
2677                bindings,
2678                pos,
2679                span: self.node_span(start_i),
2680            };
2681        }
2682        // Try `pat <- expr` with rollback.
2683        let snapshot = self.i;
2684        if let Some(pat) = self.try_bind_pattern() {
2685            if self.at_op("<-") {
2686                self.bump();
2687                let expr = self.expr();
2688                return DoStmt::Bind {
2689                    pat,
2690                    expr,
2691                    pos,
2692                    span: self.node_span(start_i),
2693                };
2694            }
2695        }
2696        self.i = snapshot;
2697        let expr = self.expr();
2698        DoStmt::Expr {
2699            expr,
2700            pos,
2701            span: self.node_span(start_i),
2702        }
2703    }
2704
2705    /// Pattern attempt for `pat <- ...`; restores nothing itself (caller
2706    /// rolls back on failure).
2707    fn try_bind_pattern(&mut self) -> Option<Pat> {
2708        self.pattern()
2709    }
2710
2711    fn let_expr(&mut self) -> Option<Expr> {
2712        let pos = self.pos();
2713        let start_i = self.i;
2714        self.bump(); // let
2715        let bindings = self.binding_block();
2716        if self.eat_keyword("in") {
2717            let body = self.expr();
2718            return Some(Expr::LetIn {
2719                bindings,
2720                body: Box::new(body),
2721                pos,
2722                span: self.node_span(start_i),
2723            });
2724        }
2725        // `let` without `in` outside a do block — degrade gracefully.
2726        Some(Expr::LetIn {
2727            bindings,
2728            body: Box::new(Expr::Error {
2729                raw: String::new(),
2730                pos,
2731                span: self.node_span(start_i),
2732            }),
2733            pos,
2734            span: self.node_span(start_i),
2735        })
2736    }
2737
2738    fn try_expr(&mut self) -> Option<Expr> {
2739        let pos = self.pos();
2740        let start_i = self.i;
2741        self.bump(); // try
2742        let body = self.expr();
2743        let mut handlers = Vec::new();
2744        self.eat(&TokenKind::VSemi);
2745        if self.eat_keyword("catch") {
2746            if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
2747                loop {
2748                    while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
2749                    match self.peek() {
2750                        None => break,
2751                        Some(TokenKind::VRBrace | TokenKind::RBrace) => {
2752                            self.bump();
2753                            break;
2754                        }
2755                        // Stray closer: discard so the loop always progresses.
2756                        Some(TokenKind::RParen | TokenKind::RBracket) => {
2757                            self.bump();
2758                            continue;
2759                        }
2760                        _ => {}
2761                    }
2762                    match self.case_alt() {
2763                        Some(a) => handlers.push(a),
2764                        None => self.skip_to_item_end(),
2765                    }
2766                }
2767            } else if let Some(a) = self.case_alt() {
2768                // Single-alternative catch on the same line.
2769                handlers.push(a);
2770            }
2771        }
2772        Some(Expr::Try {
2773            body: Box::new(body),
2774            handlers,
2775            pos,
2776            span: self.node_span(start_i),
2777        })
2778    }
2779
2780    fn lambda_expr(&mut self) -> Option<Expr> {
2781        let pos = self.pos();
2782        let start_i = self.i;
2783        self.bump(); // backslash
2784                     // `\case` — lambda-case: one implicit argument matched by the alts.
2785        if self.eat_keyword("case") {
2786            let mut alts = Vec::new();
2787            if self.eat(&TokenKind::VLBrace) || self.eat(&TokenKind::LBrace) {
2788                loop {
2789                    while self.eat(&TokenKind::VSemi) || self.eat(&TokenKind::Semi) {}
2790                    match self.peek() {
2791                        None => break,
2792                        Some(TokenKind::VRBrace | TokenKind::RBrace) => {
2793                            self.bump();
2794                            break;
2795                        }
2796                        Some(TokenKind::RParen | TokenKind::RBracket) => {
2797                            self.bump();
2798                            continue;
2799                        }
2800                        _ => {}
2801                    }
2802                    match self.case_alt() {
2803                        Some(a) => alts.push(a),
2804                        None => self.skip_to_item_end(),
2805                    }
2806                }
2807            }
2808            return Some(Expr::Lambda {
2809                params: vec![Pat::Var {
2810                    name: "_".into(),
2811                    pos,
2812                    span: Span::new(self.byte_at(start_i), self.byte_at(start_i)),
2813                }],
2814                body: Box::new(Expr::Case {
2815                    scrutinee: Box::new(Expr::Var {
2816                        qualifier: None,
2817                        name: "_".into(),
2818                        pos,
2819                        span: Span::new(self.byte_at(start_i), self.byte_at(start_i)),
2820                    }),
2821                    alts,
2822                    pos,
2823                    span: self.node_span(start_i),
2824                }),
2825                pos,
2826                span: self.node_span(start_i),
2827            });
2828        }
2829        let mut params = Vec::new();
2830        while !self.at_op("->") {
2831            match self.pattern_atom() {
2832                Some(p) => params.push(p),
2833                None => {
2834                    self.diag("bad lambda parameter");
2835                    let start = self.i;
2836                    self.skip_to_item_end();
2837                    return Some(Expr::Error {
2838                        raw: format!("\\{}", self.slice_text(start)),
2839                        pos,
2840                        span: self.node_span(start_i),
2841                    });
2842                }
2843            }
2844        }
2845        self.bump(); // ->
2846        let body = self.expr();
2847        Some(Expr::Lambda {
2848            params,
2849            body: Box::new(body),
2850            pos,
2851            span: self.node_span(start_i),
2852        })
2853    }
2854
2855    fn paren_expr(&mut self) -> Option<Expr> {
2856        let pos = self.pos();
2857        let start_i = self.i;
2858        self.bump(); // (
2859        if self.eat(&TokenKind::RParen) {
2860            return Some(Expr::Con {
2861                qualifier: None,
2862                name: "()".into(),
2863                pos,
2864                span: self.node_span(start_i),
2865            });
2866        }
2867        // Operator section / operator reference: `(+)`, `(+ 1)`.
2868        if let Some(TokenKind::Op(o)) = self.peek().cloned() {
2869            if !is_reserved_op(&o) && o != "\\" && o != "-" {
2870                self.bump();
2871                if self.eat(&TokenKind::RParen) {
2872                    return Some(Expr::Section {
2873                        op: o,
2874                        operand: None,
2875                        side: SectionSide::Right,
2876                        pos,
2877                        span: self.node_span(start_i),
2878                    });
2879                }
2880                let operand = self.expr();
2881                self.eat(&TokenKind::RParen);
2882                return Some(Expr::Section {
2883                    op: o,
2884                    operand: Some(Box::new(operand)),
2885                    side: SectionSide::Right,
2886                    pos,
2887                    span: self.node_span(start_i),
2888                });
2889            }
2890        }
2891        let first = self.expr();
2892        if self.at(&TokenKind::Comma) {
2893            let mut items = vec![first];
2894            while self.eat(&TokenKind::Comma) {
2895                items.push(self.expr());
2896            }
2897            self.eat(&TokenKind::RParen);
2898            return Some(Expr::Tuple {
2899                items,
2900                pos,
2901                span: self.node_span(start_i),
2902            });
2903        }
2904        // Left section: `(x +)`.
2905        if let Some(TokenKind::Op(o)) = self.peek().cloned() {
2906            if !is_reserved_op(&o) && self.peek_at(1) == Some(&TokenKind::RParen) {
2907                self.bump();
2908                self.bump();
2909                return Some(Expr::Section {
2910                    op: o,
2911                    operand: Some(Box::new(first)),
2912                    side: SectionSide::Left,
2913                    pos,
2914                    span: self.node_span(start_i),
2915                });
2916            }
2917        }
2918        self.eat(&TokenKind::RParen);
2919        Some(first)
2920    }
2921
2922    fn list_expr(&mut self) -> Option<Expr> {
2923        let pos = self.pos();
2924        let start_i = self.i;
2925        self.bump(); // [
2926        let mut items = Vec::new();
2927        if self.eat(&TokenKind::RBracket) {
2928            return Some(Expr::List {
2929                items,
2930                pos,
2931                span: self.node_span(start_i),
2932            });
2933        }
2934        loop {
2935            let e = self.expr();
2936            // Range: `[a .. b]` / `[a ..]`.
2937            if self.at_op("..") {
2938                self.bump();
2939                let hi = if self.at(&TokenKind::RBracket) {
2940                    Expr::Error {
2941                        raw: String::new(),
2942                        pos,
2943                        span: self.node_span(start_i),
2944                    }
2945                } else {
2946                    self.expr()
2947                };
2948                self.eat(&TokenKind::RBracket);
2949                return Some(Expr::BinOp {
2950                    op: "..".into(),
2951                    lhs: Box::new(e),
2952                    rhs: Box::new(hi),
2953                    pos,
2954                    span: self.node_span(start_i),
2955                });
2956            }
2957            // List comprehension: degrade the qualifier part to raw text.
2958            if self.at_op("|") {
2959                let start = self.i;
2960                let mut brackets = 1usize;
2961                while let Some(t) = self.peek() {
2962                    match t {
2963                        TokenKind::LBracket => brackets += 1,
2964                        TokenKind::RBracket => {
2965                            brackets -= 1;
2966                            if brackets == 0 {
2967                                break;
2968                            }
2969                        }
2970                        TokenKind::VSemi | TokenKind::VRBrace => break,
2971                        _ => {}
2972                    }
2973                    self.i += 1;
2974                }
2975                let raw = self.slice_text(start);
2976                self.eat(&TokenKind::RBracket);
2977                return Some(Expr::App {
2978                    func: Box::new(e),
2979                    args: vec![Expr::Error {
2980                        raw,
2981                        pos,
2982                        span: self.node_span(start_i),
2983                    }],
2984                    pos,
2985                    span: self.node_span(start_i),
2986                });
2987            }
2988            items.push(e);
2989            if !self.eat(&TokenKind::Comma) {
2990                break;
2991            }
2992        }
2993        self.eat(&TokenKind::RBracket);
2994        Some(Expr::List {
2995            items,
2996            pos,
2997            span: self.node_span(start_i),
2998        })
2999    }
3000}
3001
3002/// Operators that structure declarations and can never be expression infix
3003/// operators.
3004fn is_reserved_op(op: &str) -> bool {
3005    matches!(op, "=" | "<-" | "->" | "|" | ":" | "=>" | "@" | "\\" | "..")
3006}
3007
3008/// (precedence, right-assoc) — Haskell defaults; unknown operators get
3009/// infixl 9.
3010fn fixity(op: &str) -> (u8, bool) {
3011    match op {
3012        "$" | "$!" => (1, true),
3013        ">>=" | ">>" | "=<<" | "<&>" => (2, false),
3014        "||" => (3, true),
3015        "&&" => (4, true),
3016        "==" | "/=" | "<" | "<=" | ">" | ">=" => (5, false),
3017        "::" | "++" | "<>" => (6, true),
3018        "+" | "-" => (7, false),
3019        "*" | "/" => (8, false),
3020        "^" | "**" => (9, true),
3021        "." | "!!" => (10, true),
3022        _ => (9, false),
3023    }
3024}
3025
3026/// Merge type signatures and successive equations of the same function into
3027/// one `Decl::Function`, preserving first-seen order.
3028/// Bounding span of a function's equations (their first start to last end).
3029/// `None` for a signature-only function (no equations yet).
3030fn equations_extent(eqs: &[Equation]) -> Option<Span> {
3031    let mut it = eqs.iter();
3032    let first = it.next()?;
3033    let mut s = first.span;
3034    for e in it {
3035        s.start = s.start.min(e.span.start);
3036        s.end = s.end.max(e.span.end);
3037    }
3038    Some(s)
3039}
3040
3041fn merge_functions(decls: &mut Vec<Decl>) {
3042    let mut out: Vec<Decl> = Vec::with_capacity(decls.len());
3043    let mut function_index_by_name: HashMap<Identifier, usize> = HashMap::new();
3044    for decl in decls.drain(..) {
3045        match decl {
3046            Decl::Function(f) => {
3047                if let Some(existing_index) = function_index_by_name.get(&f.name).copied() {
3048                    let Decl::Function(g) = &mut out[existing_index] else {
3049                        out.push(Decl::Function(f));
3050                        continue;
3051                    };
3052                    if g.ty.is_none() {
3053                        g.ty = f.ty.clone();
3054                    }
3055                    if g.sig_span.is_none() {
3056                        g.sig_span = f.sig_span;
3057                    }
3058                    // The function's reported position is its first equation,
3059                    // not its type signature.
3060                    if g.equations.is_empty() && !f.equations.is_empty() {
3061                        g.pos = f.pos;
3062                    }
3063                    g.equations.extend(f.equations);
3064                    // A function's span is the extent of its equations, which
3065                    // are contiguous in well-formed source. A type signature
3066                    // can sit apart (with unrelated decls in between), so it is
3067                    // tracked in `sig_span` and never folded into `span` —
3068                    // doing so could straddle a sibling decl and break the
3069                    // nesting invariant.
3070                    g.span = equations_extent(&g.equations)
3071                        .or(g.sig_span)
3072                        .unwrap_or(g.span);
3073                } else {
3074                    function_index_by_name.insert(f.name.clone(), out.len());
3075                    out.push(Decl::Function(f));
3076                }
3077            }
3078            other => out.push(other),
3079        }
3080    }
3081    *decls = out;
3082}
3083
3084/// Parse a type from a slice of the type's tokens (e.g. the tokens between a
3085/// field's `:` and the item end). PURE: it never touches the main parser cursor
3086/// and never affects any span, so it is invisible to daml-fmt. Returns `None`
3087/// when the whole slice does not parse cleanly as a type.
3088///
3089/// Grammar (precedence low → high): constraint `C => T`, function `a -> b`
3090/// (right-assoc), application `head arg...` (left-assoc), then atoms (`Con`,
3091/// `Var`, `[T]`, `()`, `(T)`, `(a, b)`).
3092pub(crate) fn parse_type_from_tokens(tokens: &[Token]) -> Option<Type> {
3093    // Virtual layout tokens carry no type meaning; drop them so a stray VSemi
3094    // in the slice can't sink an otherwise-clean parse.
3095    let real_tokens: Vec<&Token> = tokens.iter().filter(|t| !t.is_virtual()).collect();
3096    if real_tokens.is_empty() {
3097        return None;
3098    }
3099    let mut parser = TypeTokenParser {
3100        tokens: &real_tokens,
3101        cursor: 0,
3102    };
3103    let ty = parser.parse_type()?;
3104    // Require the whole slice to be consumed: a partial parse means the type
3105    // had a shape we don't model, so report unknown rather than a half-truth.
3106    if parser.cursor == real_tokens.len() {
3107        Some(ty)
3108    } else {
3109        None
3110    }
3111}
3112
3113struct TypeTokenParser<'a> {
3114    tokens: &'a [&'a Token],
3115    cursor: usize,
3116}
3117
3118#[derive(Debug)]
3119struct FieldBlock {
3120    fields: Vec<FieldDecl>,
3121    dangling: bool,
3122}
3123
3124/// Result of parsing one atom: a real type, or a dropped type-level nat literal
3125/// (`Numeric 10` — not a type, so it never enters the App arg list).
3126enum TypeAtom {
3127    ParsedType(Type),
3128    DroppedLiteral(Span),
3129}
3130
3131impl<'a> TypeTokenParser<'a> {
3132    fn peek(&self) -> Option<&'a Token> {
3133        self.tokens.get(self.cursor).copied()
3134    }
3135
3136    fn eat_op(&mut self, op: &str) -> bool {
3137        if self.peek().is_some_and(|t| t.kind.is_op(op)) {
3138            self.cursor += 1;
3139            true
3140        } else {
3141            false
3142        }
3143    }
3144
3145    /// Full type: a constraint context `=> body`, or a function `a -> b`, or a
3146    /// bare application.
3147    fn parse_type(&mut self) -> Option<Type> {
3148        let lhs = if self.eat_keyword("forall") {
3149            self.parse_forall_type()?
3150        } else {
3151            self.parse_application_type()?
3152        };
3153        if self.eat_op("=>") {
3154            // `lhs` was the constraint context; drop it, keep the body.
3155            let body = self.parse_type()?;
3156            let span = Span::new(lhs.span().start, body.span().end);
3157            return Some(Type::Constrained(Box::new(body), span));
3158        }
3159        if self.eat_op("->") {
3160            let rhs = self.parse_type()?;
3161            let span = Span::new(lhs.span().start, rhs.span().end);
3162            return Some(Type::Fun(Box::new(lhs), Box::new(rhs), span));
3163        }
3164        Some(lhs)
3165    }
3166
3167    /// Application spine: one head atom applied to zero or more argument atoms.
3168    fn parse_application_type(&mut self) -> Option<Type> {
3169        let head = match self.parse_atom()? {
3170            TypeAtom::ParsedType(t) => t,
3171            // A bare nat literal is not a type.
3172            TypeAtom::DroppedLiteral(_) => return None,
3173        };
3174        let mut args = Vec::new();
3175        let start = head.span().start;
3176        let mut end = head.span().end;
3177        loop {
3178            // Only continue the spine if the next token can START an atom; an
3179            // operator (`->`, `=>`) or closer ends it.
3180            if !self.is_at_type_atom_start() {
3181                break;
3182            }
3183            match self.parse_atom()? {
3184                TypeAtom::ParsedType(t) => {
3185                    end = t.span().end;
3186                    args.push(t);
3187                }
3188                TypeAtom::DroppedLiteral(span) => {
3189                    // `Numeric 10` — drop the `10` as structure but keep it in
3190                    // the enclosing type span.
3191                    end = span.end;
3192                }
3193            }
3194        }
3195        let span = Span::new(start, end);
3196        if args.is_empty() {
3197            Some(head.with_span(span))
3198        } else {
3199            Some(Type::App(Box::new(head), args, span))
3200        }
3201    }
3202
3203    /// True if the current token can begin an atom (so the application spine
3204    /// should keep going).
3205    fn is_at_type_atom_start(&self) -> bool {
3206        matches!(
3207            self.peek().map(|t| &t.kind),
3208            Some(
3209                TokenKind::UpperId { .. }
3210                    | TokenKind::LowerId { .. }
3211                    | TokenKind::IntLit(_)
3212                    | TokenKind::DecimalLit(_)
3213                    | TokenKind::StringLit(_)
3214                    | TokenKind::CharLit(_)
3215                    | TokenKind::LBracket
3216                    | TokenKind::LParen
3217            )
3218        )
3219    }
3220
3221    fn parse_atom(&mut self) -> Option<TypeAtom> {
3222        let tok = self.peek()?;
3223        match &tok.kind {
3224            TokenKind::UpperId { qualifier, name } => {
3225                let con = Type::Con {
3226                    qualifier: qualifier.clone(),
3227                    name: name.clone(),
3228                    span: Span::new(tok.start, tok.end),
3229                };
3230                self.cursor += 1;
3231                Some(TypeAtom::ParsedType(con))
3232            }
3233            TokenKind::LowerId { name, .. } => {
3234                // Type variable (`a`, `n`). Qualified lowercase never appears in
3235                // a real type position; treat the name as the variable.
3236                let var = Type::Var(name.clone(), Span::new(tok.start, tok.end));
3237                self.cursor += 1;
3238                Some(TypeAtom::ParsedType(var))
3239            }
3240            TokenKind::IntLit(_) | TokenKind::DecimalLit(_) => {
3241                // Type-level nat literal (`Numeric 10`): consumed, but dropped.
3242                self.cursor += 1;
3243                Some(TypeAtom::DroppedLiteral(Span::new(tok.start, tok.end)))
3244            }
3245            TokenKind::StringLit(text) => {
3246                self.cursor += 1;
3247                Some(TypeAtom::ParsedType(Type::Lit {
3248                    kind: LitKind::Text,
3249                    text: text.clone(),
3250                    span: Span::new(tok.start, tok.end),
3251                }))
3252            }
3253            TokenKind::CharLit(text) => {
3254                self.cursor += 1;
3255                Some(TypeAtom::ParsedType(Type::Lit {
3256                    kind: LitKind::Char,
3257                    text: text.clone(),
3258                    span: Span::new(tok.start, tok.end),
3259                }))
3260            }
3261            TokenKind::LBracket => {
3262                let start = tok.start;
3263                self.cursor += 1;
3264                let inner = self.parse_type()?;
3265                self.eat_token(&TokenKind::RBracket).map(|end| {
3266                    TypeAtom::ParsedType(Type::List(Box::new(inner), Span::new(start, end.end)))
3267                })
3268            }
3269            TokenKind::LParen => {
3270                let start = tok.start;
3271                self.cursor += 1;
3272                if let Some(op) = self.eat_token_if_operator() {
3273                    let mut name = op.as_str().to_string();
3274                    while matches!(
3275                        self.tokens.get(self.cursor).map(|t| &t.kind),
3276                        Some(TokenKind::Op(_))
3277                    ) {
3278                        self.cursor += 1;
3279                        if let TokenKind::Op(o) = &self.tokens[self.cursor - 1].kind {
3280                            name.push_str(o.as_str());
3281                        }
3282                    }
3283                    if self.eat_token(&TokenKind::RParen).is_some()
3284                        && self
3285                            .tokens
3286                            .get(self.cursor)
3287                            .is_some_and(|t| Self::is_type_atom_start(&t.kind))
3288                    {
3289                        let end = self.tokens[self.cursor - 1];
3290                        return Some(TypeAtom::ParsedType(Type::Con {
3291                            qualifier: None,
3292                            name: name.into(),
3293                            span: Span::new(start, end.end),
3294                        }));
3295                    }
3296                    return None;
3297                }
3298                if matches!(
3299                    self.tokens.get(self.cursor).map(|t| &t.kind),
3300                    Some(TokenKind::Comma)
3301                ) {
3302                    let mut name = String::from(",");
3303                    self.cursor += 1;
3304                    while matches!(
3305                        self.tokens.get(self.cursor).map(|t| &t.kind),
3306                        Some(TokenKind::Comma)
3307                    ) {
3308                        self.cursor += 1;
3309                        name.push(',');
3310                    }
3311                    if self.eat_token(&TokenKind::RParen).is_some()
3312                        && self
3313                            .tokens
3314                            .get(self.cursor)
3315                            .is_some_and(|t| Self::is_type_atom_start(&t.kind))
3316                    {
3317                        let end = self.tokens[self.cursor - 1];
3318                        return Some(TypeAtom::ParsedType(Type::Con {
3319                            qualifier: None,
3320                            name: name.into(),
3321                            span: Span::new(start, end.end),
3322                        }));
3323                    }
3324                    return None;
3325                }
3326                if let Some(end) = self.eat_token(&TokenKind::RParen) {
3327                    // ()
3328                    return Some(TypeAtom::ParsedType(Type::Unit(Span::new(start, end.end))));
3329                }
3330                let first = self.parse_type()?;
3331                if self.peek().map(|t| &t.kind) == Some(&TokenKind::Comma) {
3332                    let mut items = vec![first];
3333                    while self.eat_token(&TokenKind::Comma).is_some() {
3334                        items.push(self.parse_type()?);
3335                    }
3336                    self.eat_token(&TokenKind::RParen).map(|end| {
3337                        TypeAtom::ParsedType(Type::Tuple(items, Span::new(start, end.end)))
3338                    })
3339                } else {
3340                    self.eat_token(&TokenKind::RParen).map(|end| {
3341                        // Grouping parens.
3342                        TypeAtom::ParsedType(first.with_span(Span::new(start, end.end)))
3343                    })
3344                }
3345            }
3346            _ => None,
3347        }
3348    }
3349
3350    fn eat_keyword(&mut self, kw: &str) -> bool {
3351        if self.peek().is_some_and(|t| t.kind.is_keyword(kw)) {
3352            self.cursor += 1;
3353            true
3354        } else {
3355            false
3356        }
3357    }
3358
3359    fn parse_forall_type(&mut self) -> Option<Type> {
3360        let start = self
3361            .tokens
3362            .get(self.cursor.wrapping_sub(1))
3363            .map(|t| t.start)
3364            .unwrap_or_default();
3365        while self.cursor < self.tokens.len() {
3366            if self.peek().is_some_and(|t| t.kind.is_op(".")) {
3367                self.cursor += 1;
3368                let body = self.parse_type()?;
3369                let body_span = body.span();
3370                return Some(body.with_span(Span::new(start, body_span.end)));
3371            }
3372            self.cursor += 1;
3373        }
3374        None
3375    }
3376
3377    fn eat_token(&mut self, tok: &TokenKind) -> Option<&'a Token> {
3378        if self.peek().is_some_and(|t| t.kind == *tok) {
3379            let t = self.peek();
3380            self.cursor += 1;
3381            t
3382        } else {
3383            None
3384        }
3385    }
3386
3387    fn eat_token_if_operator(&mut self) -> Option<&'a Operator> {
3388        match self.peek() {
3389            Some(Token {
3390                kind: TokenKind::Op(op),
3391                ..
3392            }) => {
3393                self.cursor += 1;
3394                Some(op)
3395            }
3396            _ => None,
3397        }
3398    }
3399
3400    const fn is_type_atom_start(kind: &TokenKind) -> bool {
3401        matches!(
3402            kind,
3403            TokenKind::UpperId { .. }
3404                | TokenKind::LowerId { .. }
3405                | TokenKind::IntLit(_)
3406                | TokenKind::DecimalLit(_)
3407                | TokenKind::StringLit(_)
3408                | TokenKind::CharLit(_)
3409                | TokenKind::LParen
3410                | TokenKind::LBracket
3411        )
3412    }
3413}
3414
3415fn render_token_slice(tokens: &[Token]) -> String {
3416    let mut s = String::new();
3417    let mut prev_no_space_after = true;
3418    for t in tokens {
3419        let (text, no_space_before, no_space_after): (String, bool, bool) = match &t.kind {
3420            TokenKind::LowerId { qualifier, name } | TokenKind::UpperId { qualifier, name } => (
3421                qualifier
3422                    .as_ref()
3423                    .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
3424                false,
3425                false,
3426            ),
3427            TokenKind::Op(o) => (o.to_string(), false, false),
3428            TokenKind::IntLit(n) | TokenKind::DecimalLit(n) => (n.clone(), false, false),
3429            TokenKind::StringLit(v) => (format!("{v:?}"), false, false),
3430            TokenKind::CharLit(v) => (format!("'{v}'"), false, false),
3431            TokenKind::LParen => ("(".to_string(), false, true),
3432            TokenKind::RParen => (")".to_string(), true, false),
3433            TokenKind::LBracket => ("[".to_string(), false, true),
3434            TokenKind::RBracket => ("]".to_string(), true, false),
3435            TokenKind::LBrace => ("{".to_string(), false, true),
3436            TokenKind::RBrace => ("}".to_string(), true, false),
3437            TokenKind::Comma => (",".to_string(), true, false),
3438            TokenKind::Semi | TokenKind::VSemi => (";".to_string(), true, false),
3439            TokenKind::Backtick => ("`".to_string(), false, false),
3440            TokenKind::VLBrace | TokenKind::VRBrace => continue,
3441        };
3442        if !s.is_empty() && !no_space_before && !prev_no_space_after {
3443            s.push(' ');
3444        }
3445        s.push_str(&text);
3446        prev_no_space_after = no_space_after;
3447    }
3448    s
3449}
3450
3451#[cfg(test)]
3452mod type_tests {
3453    use super::*;
3454    use crate::lexer::lex;
3455
3456    /// Parse a bare type string straight through the lexer. A single-line type
3457    /// has no layout-significant newlines, so no virtual tokens appear — this
3458    /// exercises the type grammar in isolation.
3459    fn ty(s: &str) -> Option<Type> {
3460        let (toks, errs) = lex(s).into_parts();
3461        assert!(errs.is_empty(), "lex errors for {s:?}: {errs:?}");
3462        parse_type_from_tokens(&toks)
3463    }
3464
3465    fn con(name: &str) -> Type {
3466        Type::Con {
3467            qualifier: None,
3468            name: name.into(),
3469            span: Span::default(),
3470        }
3471    }
3472
3473    fn qualified_con(qualifier: &str, name: &str) -> Type {
3474        Type::Con {
3475            qualifier: Some(qualifier.into()),
3476            name: name.into(),
3477            span: Span::default(),
3478        }
3479    }
3480
3481    fn app(head: Type, args: Vec<Type>) -> Type {
3482        Type::App(Box::new(head), args, Span::default())
3483    }
3484
3485    fn list(inner: Type) -> Type {
3486        Type::List(Box::new(inner), Span::default())
3487    }
3488
3489    fn tuple(items: Vec<Type>) -> Type {
3490        Type::Tuple(items, Span::default())
3491    }
3492
3493    fn fun(param: Type, result: Type) -> Type {
3494        Type::Fun(Box::new(param), Box::new(result), Span::default())
3495    }
3496
3497    fn var(name: &str) -> Type {
3498        Type::Var(name.into(), Span::default())
3499    }
3500
3501    fn unit() -> Type {
3502        Type::Unit(Span::default())
3503    }
3504
3505    fn constrained(body: Type) -> Type {
3506        Type::Constrained(Box::new(body), Span::default())
3507    }
3508
3509    fn text_lit(value: &str) -> Type {
3510        Type::Lit {
3511            kind: LitKind::Text,
3512            text: value.to_string(),
3513            span: Span::default(),
3514        }
3515    }
3516
3517    fn char_lit(value: &str) -> Type {
3518        Type::Lit {
3519            kind: LitKind::Char,
3520            text: value.to_string(),
3521            span: Span::default(),
3522        }
3523    }
3524
3525    #[test]
3526    fn atoms() {
3527        assert_eq!(ty("Party"), Some(con("Party")));
3528        assert_eq!(ty("Decimal"), Some(con("Decimal")));
3529        assert_eq!(ty("a"), Some(var("a")));
3530        assert_eq!(ty("()"), Some(unit()));
3531    }
3532
3533    #[test]
3534    fn application_vs_constructor() {
3535        // The whole point of the new model: `ContractId Foo` is an APPLICATION,
3536        // not one opaque name.
3537        assert_eq!(
3538            ty("ContractId Foo"),
3539            Some(app(con("ContractId"), vec![con("Foo")]))
3540        );
3541        assert_eq!(
3542            ty("Optional (ContractId Foo)"),
3543            Some(app(
3544                con("Optional"),
3545                vec![app(con("ContractId"), vec![con("Foo")])]
3546            ))
3547        );
3548        assert_eq!(
3549            ty("Map Text Int"),
3550            Some(app(con("Map"), vec![con("Text"), con("Int")]))
3551        );
3552    }
3553
3554    #[test]
3555    fn qualified_constructor_keeps_qualifier() {
3556        assert_eq!(
3557            ty("DA.Map.Map Text Int"),
3558            Some(app(
3559                qualified_con("DA.Map", "Map"),
3560                vec![con("Text"), con("Int")]
3561            ))
3562        );
3563    }
3564
3565    #[test]
3566    fn list_and_tuple() {
3567        assert_eq!(ty("[Text]"), Some(list(con("Text"))));
3568        assert_eq!(
3569            ty("(Int, Text)"),
3570            Some(tuple(vec![con("Int"), con("Text")]))
3571        );
3572        // A tuple is NOT a grouping paren — must stay a Tuple, never collapse.
3573        assert_eq!(
3574            ty("(a, b, c)"),
3575            Some(tuple(vec![var("a"), var("b"), var("c")]))
3576        );
3577        // Single grouping paren unwraps.
3578        assert_eq!(ty("(Text)"), Some(con("Text")));
3579    }
3580
3581    #[test]
3582    fn function_types_are_arrows_not_names() {
3583        // These are exactly the corpus strings the old matcher swallowed into
3584        // one opaque `Named`.
3585        assert_eq!(ty("Int -> Int"), Some(fun(con("Int"), con("Int"))));
3586        // Right associativity: `a -> b -> c` == `a -> (b -> c)`.
3587        assert_eq!(
3588            ty("Int -> Text -> Bool"),
3589            Some(fun(con("Int"), fun(con("Text"), con("Bool"))))
3590        );
3591        assert_eq!(
3592            ty("Party -> Script ()"),
3593            Some(fun(con("Party"), app(con("Script"), vec![unit()])))
3594        );
3595    }
3596
3597    #[test]
3598    fn script_application() {
3599        // `Script ()` ×147 in the corpus — an application flattened to `Named`
3600        // before. Now a real App.
3601        assert_eq!(ty("Script ()"), Some(app(con("Script"), vec![unit()])));
3602    }
3603
3604    #[test]
3605    fn numeric_nat_literal_is_dropped() {
3606        // `Numeric 10`: the `10` is a type-level nat, not a type, so it drops
3607        // and the head Con stands alone. `Numeric n` keeps the type variable.
3608        assert_eq!(ty("Numeric 10"), Some(con("Numeric")));
3609        assert_eq!(ty("Numeric n"), Some(app(con("Numeric"), vec![var("n")])));
3610    }
3611
3612    #[test]
3613    fn string_and_char_type_literals_are_structured() {
3614        // `HasField "observers"` — the field name is a type-level string
3615        // literal, not opaque syntax that collapses to malformed diagnostics.
3616        assert_eq!(
3617            ty(r#"HasField "observers""#),
3618            Some(app(con("HasField"), vec![text_lit("observers")]))
3619        );
3620        assert_eq!(
3621            ty(r#"HasField "observers" t PartiesMap"#),
3622            Some(app(
3623                con("HasField"),
3624                vec![text_lit("observers"), var("t"), con("PartiesMap")]
3625            ))
3626        );
3627        assert_eq!(
3628            ty(r"HasField 'x'"),
3629            Some(app(con("HasField"), vec![char_lit("x")]))
3630        );
3631    }
3632
3633    #[test]
3634    fn type_literal_in_function_signature_is_not_malformed() {
3635        let src = r#"module M where
3636f : HasField "observers" t PartiesMap => ()
3637  = ()
3638"#;
3639        let (module, diagnostics) = parse_module(src).into_parts();
3640        assert!(
3641            !diagnostics
3642                .iter()
3643                .any(|d| d.message.contains("malformed function type annotation")),
3644            "constraint with type string literal must parse cleanly: {diagnostics:#?}"
3645        );
3646        let function = match &module.decls[0] {
3647            Decl::Function(f) => f,
3648            other => panic!("expected function, got {other:?}"),
3649        };
3650        let ty = function.ty.as_ref().expect("function signature type");
3651        assert!(matches!(
3652            ty,
3653            Type::Constrained(body, _)
3654                if matches!(
3655                    &**body,
3656                    Type::Unit(_)
3657                )
3658        ));
3659    }
3660
3661    #[test]
3662    fn constraint_context_is_dropped_body_kept() {
3663        // `NumericScale n => Numeric 37 -> Numeric n` — a constrained function.
3664        // Context dropped; body (the arrow) kept.
3665        assert_eq!(
3666            ty("NumericScale n => Numeric 37 -> Numeric n"),
3667            Some(constrained(fun(
3668                con("Numeric"),
3669                app(con("Numeric"), vec![var("n")])
3670            )))
3671        );
3672        // Tuple context `(Eq a, Show a) => a` also drops cleanly.
3673        assert_eq!(ty("(Eq a, Show a) => a"), Some(constrained(var("a"))));
3674    }
3675
3676    #[test]
3677    fn unparseable_is_none() {
3678        // A trailing arrow with no body is not a clean type → unknown (None),
3679        // never a half-parse.
3680        assert_eq!(ty("Int ->"), None);
3681        assert_eq!(ty("-> Int"), None);
3682    }
3683
3684    #[test]
3685    fn ty_is_populated_through_real_parse() {
3686        // End-to-end: the wiring actually fills `ty` on template fields and the
3687        // choice return type, from the real token stream.
3688        let src = r#"module M where
3689template T
3690  with
3691    owner : Party
3692    held : ContractId Asset
3693  where
3694    signatory owner
3695    choice Go : Optional (ContractId Asset)
3696      controller owner
3697      do
3698        pure None
3699"#;
3700        let (m, _) = parse_module(src).into_parts();
3701        let t = match &m.decls[0] {
3702            Decl::Template(t) => t,
3703            other => panic!("expected template, got {other:?}"),
3704        };
3705        assert_eq!(t.fields[0].ty, Some(con("Party")));
3706        assert_eq!(
3707            t.fields[1].ty,
3708            Some(app(con("ContractId"), vec![con("Asset")]))
3709        );
3710        let choice = match &t
3711            .body
3712            .iter()
3713            .find(|d| matches!(d, TemplateBodyDecl::Choice(_)))
3714        {
3715            Some(TemplateBodyDecl::Choice(c)) => (*c).clone(),
3716            _ => panic!("expected choice"),
3717        };
3718        assert_eq!(
3719            choice.return_ty,
3720            Some(app(
3721                con("Optional"),
3722                vec![app(con("ContractId"), vec![con("Asset")])]
3723            ))
3724        );
3725    }
3726
3727    #[test]
3728    fn ty_is_populated_on_key_and_interface_method() {
3729        // The other two type-bearing nodes: a template `key ... : T` and an
3730        // interface method signature both fill `ty` from the token stream.
3731        let src = r#"module M where
3732template T
3733  with
3734    owner : Party
3735  where
3736    signatory owner
3737    key owner : Party
3738    maintainer owner
3739
3740interface I where
3741  getAmount : Numeric 10
3742"#;
3743        let (m, _) = parse_module(src).into_parts();
3744        let t = match &m.decls[0] {
3745            Decl::Template(t) => t,
3746            other => panic!("expected template, got {other:?}"),
3747        };
3748        let key_ty = t.body.iter().find_map(|d| match d {
3749            TemplateBodyDecl::Key { ty, .. } => Some(ty.clone()),
3750            _ => None,
3751        });
3752        assert_eq!(key_ty, Some(Some(con("Party"))));
3753
3754        let iface = match &m.decls[1] {
3755            Decl::Interface(i) => i,
3756            other => panic!("expected interface, got {other:?}"),
3757        };
3758        // `Numeric 10` — the nat literal is dropped, leaving the bare head Con.
3759        assert_eq!(iface.methods[0].ty, Some(con("Numeric")));
3760    }
3761
3762    #[test]
3763    fn interface_instance_with_for_sets_explicit_template() {
3764        let src = r#"module M where
3765template Account
3766  with
3767    owner : Party
3768  where
3769    signatory owner
3770    interface instance Disclosure.I for Account where
3771      disclose = owner
3772"#;
3773        let (module, diagnostics) = parse_module(src).into_parts();
3774        assert!(
3775            diagnostics.is_empty(),
3776            "expected no diagnostics, got {diagnostics:?}"
3777        );
3778        let template = match &module.decls[0] {
3779            Decl::Template(t) => t,
3780            other => panic!("expected template, got {other:?}"),
3781        };
3782        let instance = template
3783            .body
3784            .iter()
3785            .find_map(|decl| match decl {
3786                TemplateBodyDecl::InterfaceInstance(ii) => Some(ii),
3787                _ => None,
3788            })
3789            .expect("template body should contain an interface instance");
3790        assert_eq!(instance.interface_name.as_str(), "Disclosure.I");
3791        assert_eq!(
3792            instance.for_template.as_ref().map(ModuleName::as_str),
3793            Some("Account")
3794        );
3795    }
3796
3797    #[test]
3798    fn interface_instance_without_for_leaves_template_absent() {
3799        let src = r#"module M where
3800template Account
3801  with
3802    owner : Party
3803  where
3804    signatory owner
3805    interface instance Disclosure.I where
3806      disclose = owner
3807"#;
3808        let (module, diagnostics) = parse_module(src).into_parts();
3809        assert!(
3810            diagnostics.is_empty(),
3811            "omitted 'for' is valid inside a template, got {diagnostics:?}"
3812        );
3813        let template = match &module.decls[0] {
3814            Decl::Template(t) => t,
3815            other => panic!("expected template, got {other:?}"),
3816        };
3817        let instance = template
3818            .body
3819            .iter()
3820            .find_map(|decl| match decl {
3821                TemplateBodyDecl::InterfaceInstance(ii) => Some(ii),
3822                _ => None,
3823            })
3824            .expect("template body should contain an interface instance");
3825        assert_eq!(instance.interface_name.as_str(), "Disclosure.I");
3826        assert!(
3827            instance.for_template.is_none(),
3828            "omitted 'for' must be None, not an empty ModuleName sentinel"
3829        );
3830    }
3831
3832    #[test]
3833    fn interface_instance_missing_template_after_for_is_malformed() {
3834        let src = r#"module M where
3835template Account
3836  with
3837    owner : Party
3838  where
3839    signatory owner
3840    interface instance Disclosure.I for where
3841      disclose = owner
3842"#;
3843        let (module, diagnostics) = parse_module(src).into_parts();
3844        assert!(
3845            diagnostics.iter().any(|d| {
3846                d.category == DiagnosticCategory::Malformed
3847                    && d.message == "interface instance missing template name after 'for'"
3848            }),
3849            "expected malformed diagnostic for missing template after 'for', got {diagnostics:?}"
3850        );
3851        let template = match &module.decls[0] {
3852            Decl::Template(t) => t,
3853            other => panic!("expected template, got {other:?}"),
3854        };
3855        let instance = template
3856            .body
3857            .iter()
3858            .find_map(|decl| match decl {
3859                TemplateBodyDecl::InterfaceInstance(ii) => Some(ii),
3860                _ => None,
3861            })
3862            .expect("parser should still recover an interface instance node");
3863        assert!(instance.for_template.is_none());
3864    }
3865
3866    #[test]
3867    fn malformed_guarded_equation_reports_missing_equals_and_continues() {
3868        let src = "module M where\nf x | x > 0\ng = 1\n";
3869        let (module, diagnostics) = parse_module(src).into_parts();
3870
3871        assert!(
3872            diagnostics.iter().any(
3873                |diagnostic| diagnostic.message == "expected '=' after guard"
3874                    && diagnostic.category == DiagnosticCategory::Malformed
3875            ),
3876            "expected guard diagnostic, got {diagnostics:?}"
3877        );
3878        assert!(
3879            module
3880                .decls
3881                .iter()
3882                .any(|decl| matches!(decl, Decl::Function(function) if function.name == "g")),
3883            "parser should recover to the following declaration: {:?}",
3884            module.decls
3885        );
3886    }
3887
3888    #[test]
3889    fn malformed_brackets_do_not_underflow_recovery_scans() {
3890        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";
3891        let (module, _diagnostics) = parse_module(src).into_parts();
3892
3893        assert_eq!(module.name, "M");
3894        assert!(
3895            module
3896                .decls
3897                .iter()
3898                .any(|decl| matches!(decl, Decl::Function(function) if function.name == "g")),
3899            "parser should recover to the following declaration: {:?}",
3900            module.decls
3901        );
3902    }
3903
3904    #[test]
3905    fn headerless_file_keeps_legacy_unknown_name_fallback() {
3906        let (module, _diagnostics) = parse_module("f = 1\n").into_parts();
3907
3908        assert_eq!(module.name, "Unknown");
3909        assert!(
3910            module
3911                .decls
3912                .iter()
3913                .any(|decl| matches!(decl, Decl::Function(function) if function.name == "f")),
3914            "expected function declaration to be parsed: {:?}",
3915            module.decls
3916        );
3917    }
3918}
3919
3920#[cfg(test)]
3921mod parser_tests {
3922    use super::*;
3923
3924    fn parse(src: &str) -> (Module, Vec<ParseDiagnostic>) {
3925        parse_module(src).into_parts()
3926    }
3927
3928    fn section_side_for_fn(module: &Module, name: &str) -> SectionSide {
3929        let body = get_first_equation_body(module, name);
3930        match body {
3931            Expr::Section { side, .. } => *side,
3932            other => panic!("expected section body for {name}, got {other:?}"),
3933        }
3934    }
3935
3936    #[test]
3937    fn import_style_distinguishes_qualified_prefix_and_postfix() {
3938        let (module, diagnostics) = parse(
3939            "module M where
3940import qualified Foo.Bar as FB
3941import DA.Map qualified as Map
3942import Baz as B",
3943        );
3944
3945        assert!(diagnostics.is_empty());
3946        assert_eq!(
3947            module.imports.iter().map(|i| i.style).collect::<Vec<_>>(),
3948            vec![
3949                ImportStyle::Qualified,
3950                ImportStyle::Qualified,
3951                ImportStyle::Unqualified,
3952            ]
3953        );
3954    }
3955
3956    #[test]
3957    fn expression_sections_encode_side_in_ast() {
3958        let (module, diagnostics) = parse(
3959            "module M where
3960f = (+ 1)
3961g = (+)
3962",
3963        );
3964
3965        assert!(diagnostics.is_empty());
3966        assert!(matches!(
3967            get_first_equation_body(&module, "f"),
3968            Expr::Section {
3969                operand: Some(_),
3970                ..
3971            }
3972        ));
3973        assert!(matches!(
3974            get_first_equation_body(&module, "g"),
3975            Expr::Section { operand: None, .. }
3976        ));
3977        assert_eq!(section_side_for_fn(&module, "f"), SectionSide::Right);
3978        assert_eq!(section_side_for_fn(&module, "g"), SectionSide::Right);
3979    }
3980
3981    #[test]
3982    fn do_expr_is_allowed_for_top_level_expression_parsing() {
3983        let (module, diagnostics) = parse(
3984            "module M where
3985f = do
3986  pure True
3987",
3988        );
3989
3990        assert!(diagnostics.is_empty());
3991        assert!(matches!(
3992            get_first_equation_body(&module, "f"),
3993            Expr::Do { .. }
3994        ));
3995    }
3996
3997    #[test]
3998    fn do_expr_is_disallowed_for_case_scrutinee_parsing() {
3999        let (module, diagnostics) = parse(
4000            "module M where
4001f = case do 1 of
4002  x -> x
4003",
4004        );
4005
4006        assert!(diagnostics
4007            .iter()
4008            .any(|d| d.message == "expected 'of' in case expression"));
4009        assert!(matches!(
4010            get_first_equation_body(&module, "f"),
4011            Expr::Error { .. }
4012        ));
4013    }
4014
4015    fn get_first_equation_body<'a>(module: &'a Module, name: &str) -> &'a Expr {
4016        let function = module
4017            .decls
4018            .iter()
4019            .find_map(|d| match d {
4020                Decl::Function(f) if f.name == name => Some(f),
4021                _ => None,
4022            })
4023            .unwrap_or_else(|| panic!("missing function declaration {name}"));
4024        let first_equation = function
4025            .equations
4026            .first()
4027            .unwrap_or_else(|| panic!("missing equation for function {name}"));
4028        &first_equation.body
4029    }
4030}
4031
4032#[cfg(test)]
4033mod strict_parse_tests {
4034    use super::*;
4035    use crate::ast::DiagnosticCategory;
4036
4037    #[test]
4038    fn strict_parse_accepts_clean_module() {
4039        let src = "module M where\nfoo : Int\nfoo = 1\n";
4040        let module = parse_module_strict(src).expect("clean source must parse strictly");
4041        assert_eq!(module.name, "M");
4042    }
4043
4044    #[test]
4045    fn strict_parse_rejects_malformed_source_while_tolerant_parse_keeps_diagnostics() {
4046        let src = "module M where\nf x | x > 0\ng = 1\n";
4047        let tolerant = parse_module(src);
4048        assert!(
4049            tolerant
4050                .diagnostics
4051                .iter()
4052                .any(|d| d.category == DiagnosticCategory::Malformed),
4053            "tolerant parse must record malformed guard without aborting"
4054        );
4055
4056        let err = parse_module_strict(src).expect_err("malformed guard must fail strict parse");
4057        assert_eq!(err.diagnostics(), &tolerant.diagnostics);
4058        assert!(
4059            err.module()
4060                .decls
4061                .iter()
4062                .any(|decl| matches!(decl, Decl::Function(f) if f.name == "g")),
4063            "strict error should still carry the partial module tolerant parsing produced"
4064        );
4065    }
4066
4067    #[test]
4068    fn into_result_matches_parse_module_strict() {
4069        let src = "module M where\n%%% junk\n";
4070        let tolerant = parse_module(src);
4071        assert!(!tolerant.diagnostics.is_empty());
4072
4073        let strict = parse_module_strict(src).expect_err("junk decl must fail strict parse");
4074        let converted = tolerant
4075            .into_result()
4076            .expect_err("same diagnostics via into_result");
4077        assert_eq!(strict.diagnostics(), converted.diagnostics());
4078    }
4079}