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