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