Skip to main content

bock_parser/
lib.rs

1//! Bock parser — transforms a token stream into a typed abstract syntax tree.
2//!
3//! # Usage
4//! ```ignore
5//! let parser = Parser::new(tokens, &source_file);
6//! let module = parser.parse_module();
7//! ```
8
9use bock_ast::{
10    Annotation, AnnotationArg, Arg, AssignOp, AssociatedType, BinOp, Block, ClassDecl, ConstDecl,
11    EffectDecl, EnumDecl, EnumVariant, Expr, FnDecl, ForLoop, GenericParam, GuardStmt, HandlerPair,
12    HandlingBlock, Ident, ImplBlock, ImportDecl, ImportItems, ImportedName, InterpolationPart,
13    Item, LetStmt, Literal, MatchArm, Module, ModuleHandleDecl, ModulePath, NodeId, Param, Pattern,
14    RecordDecl, RecordDeclField, RecordField, RecordPatternField, RecordSpread, Stmt, TraitDecl,
15    TypeAliasDecl, TypeConstraint, TypeExpr, TypePath, UnaryOp, Visibility, WhileLoop,
16};
17use bock_errors::{DiagnosticBag, DiagnosticCode, Span};
18use bock_lexer::{Token, TokenKind};
19use bock_source::SourceFile;
20
21/// Internal tag for binary operators during precedence climbing.
22#[derive(Debug, Clone, PartialEq)]
23enum OpTag {
24    Assign(AssignOp),
25    Pipe,
26    Compose,
27    Range,
28    RangeInclusive,
29    Binary(BinOp),
30    Is,
31}
32
33/// The Bock parser. Transforms a flat token stream into a typed [`Module`] AST.
34///
35/// Create with [`Parser::new`], then call [`Parser::parse_module`].
36pub struct Parser<'src> {
37    tokens: Vec<Token>,
38    pos: usize,
39    source: &'src SourceFile,
40    diagnostics: DiagnosticBag,
41    next_id: NodeId,
42    /// Tracks consecutive parse errors for panic-mode recovery.
43    consecutive_errors: usize,
44}
45
46impl<'src> Parser<'src> {
47    /// Create a new parser from a token stream and its source file.
48    ///
49    /// The token stream must contain at least one token (the `Eof` sentinel).
50    #[must_use]
51    pub fn new(tokens: Vec<Token>, source: &'src SourceFile) -> Self {
52        assert!(
53            !tokens.is_empty(),
54            "token list must contain at least an EOF token"
55        );
56        Self {
57            tokens,
58            pos: 0,
59            source,
60            diagnostics: DiagnosticBag::new(),
61            next_id: 0,
62            consecutive_errors: 0,
63        }
64    }
65
66    /// Parse the full token stream as a source file, returning the root [`Module`].
67    pub fn parse_module(&mut self) -> Module {
68        let start_span = self.peek().span;
69
70        // Collect module-level doc comments (`//!`).
71        // Skip leading newlines so `//!` is found even after regular `//` comments.
72        self.skip_newlines();
73        let mut doc = Vec::new();
74        while self.at(TokenKind::ModuleDocComment) {
75            let tok = self.advance();
76            if let Some(text) = tok.literal {
77                doc.push(text);
78            }
79            self.skip_newlines();
80        }
81        self.skip_newlines();
82
83        // Optional `module path.name` declaration.
84        let path = if self.at(TokenKind::Module) {
85            Some(self.parse_module_decl())
86        } else {
87            None
88        };
89        self.skip_newlines();
90
91        // Collect module-level doc comments (`//!`) that appear after the
92        // module declaration.  The spec allows `//!` both before and after
93        // `module name`, so we absorb them here too.
94        while self.at(TokenKind::ModuleDocComment) {
95            let tok = self.advance();
96            if let Some(text) = tok.literal {
97                doc.push(text);
98            }
99            self.skip_newlines();
100        }
101        self.skip_newlines();
102
103        // Import declarations (`use ...` and `public use ...`).
104        let mut imports = Vec::new();
105        loop {
106            self.skip_newlines();
107            if self.at(TokenKind::Use) {
108                imports.push(self.parse_import_decl(Visibility::Private));
109            } else if self.at_visibility() && self.peek_kind_at(1) == Some(TokenKind::Use) {
110                let vis = self.parse_visibility();
111                imports.push(self.parse_import_decl(vis));
112            } else {
113                break;
114            }
115        }
116        self.skip_newlines();
117
118        // Top-level items.
119        let items = self.parse_items();
120
121        let end_span = self.peek().span;
122        Module {
123            id: self.alloc_id(),
124            span: Span::merge(start_span, end_span),
125            doc,
126            path,
127            imports,
128            items,
129        }
130    }
131
132    /// Returns a reference to the accumulated diagnostics.
133    #[must_use]
134    pub fn diagnostics(&self) -> &DiagnosticBag {
135        &self.diagnostics
136    }
137
138    // ─── Token-stream primitives ──────────────────────────────────────────────
139
140    /// Look at the current token without consuming it.
141    pub(crate) fn peek(&self) -> &Token {
142        &self.tokens[self.pos]
143    }
144
145    /// Consume and return the current token, advancing the cursor.
146    pub(crate) fn advance(&mut self) -> Token {
147        let tok = self.tokens[self.pos].clone();
148        if self.pos + 1 < self.tokens.len() {
149            self.pos += 1;
150        }
151        tok
152    }
153
154    /// Returns `true` if the current token has the given `kind`.
155    #[must_use]
156    pub(crate) fn at(&self, kind: TokenKind) -> bool {
157        self.peek().kind == kind
158    }
159
160    /// Consume the current token if it matches `kind`; otherwise emit an error diagnostic.
161    pub(crate) fn expect(&mut self, kind: TokenKind) -> Result<Token, ()> {
162        if self.at(kind.clone()) {
163            Ok(self.advance())
164        } else {
165            let span = self.peek().span;
166            let found = self.peek().kind.clone();
167            self.diagnostics.error(
168                DiagnosticCode {
169                    prefix: 'E',
170                    number: 2000,
171                },
172                format!("expected `{kind}`, found `{found}`"),
173                span,
174            );
175            Err(())
176        }
177    }
178
179    /// Skip over any [`TokenKind::Newline`] tokens.
180    pub(crate) fn skip_newlines(&mut self) {
181        while self.at(TokenKind::Newline) {
182            let _ = self.advance();
183        }
184    }
185
186    /// Return the kind of the first non-newline token at or after the cursor,
187    /// without advancing.
188    fn peek_past_newlines_kind(&self) -> Option<TokenKind> {
189        let mut i = self.pos;
190        while i < self.tokens.len() && self.tokens[i].kind == TokenKind::Newline {
191            i += 1;
192        }
193        self.tokens.get(i).map(|t| t.kind.clone())
194    }
195
196    // ─── Private helpers ──────────────────────────────────────────────────────
197
198    fn alloc_id(&mut self) -> NodeId {
199        let id = self.next_id;
200        self.next_id += 1;
201        id
202    }
203
204    /// Peek at the kind of the token `offset` positions ahead of the cursor.
205    fn peek_kind_at(&self, offset: usize) -> Option<TokenKind> {
206        self.tokens.get(self.pos + offset).map(|t| t.kind.clone())
207    }
208
209    fn at_visibility(&self) -> bool {
210        matches!(self.peek().kind, TokenKind::Public | TokenKind::Internal)
211    }
212
213    /// Returns `true` if the current token starts a top-level declaration.
214    fn at_decl_start(&self) -> bool {
215        matches!(
216            self.peek().kind,
217            TokenKind::Fn
218                | TokenKind::Async
219                | TokenKind::Record
220                | TokenKind::Enum
221                | TokenKind::Class
222                | TokenKind::Trait
223                | TokenKind::Platform
224                | TokenKind::Impl
225                | TokenKind::Effect
226                | TokenKind::Const
227                | TokenKind::Type
228                | TokenKind::Use
229                | TokenKind::Handle
230                | TokenKind::At
231                | TokenKind::Public
232                | TokenKind::Internal
233        )
234    }
235
236    /// Synchronize the parser after an error by skipping tokens until a safe
237    /// restart point: a declaration-starting keyword, `}`, `;`, or EOF.
238    ///
239    /// Returns the span of all tokens skipped.
240    fn synchronize(&mut self) -> Span {
241        let start = self.peek().span;
242        let mut end = start;
243        while !self.at(TokenKind::Eof) {
244            let kind = self.peek().kind.clone();
245            match kind {
246                // Synchronization points — stop before consuming
247                TokenKind::Semicolon | TokenKind::RBrace => {
248                    let _ = self.advance(); // consume the sync token
249                    end = self.peek().span;
250                    break;
251                }
252                TokenKind::Newline => {
253                    let _ = self.advance();
254                    // If the next non-newline is a decl start, stop here
255                    if self.at_decl_start() || self.at(TokenKind::Eof) {
256                        break;
257                    }
258                }
259                _ if self.at_decl_start() => break,
260                _ => {
261                    end = self.peek().span;
262                    let _ = self.advance();
263                }
264            }
265        }
266        Span::merge(start, end)
267    }
268
269    /// Panic-mode recovery: skip all tokens until the next top-level declaration
270    /// keyword (or EOF), consuming any intermediate braces/semicolons too.
271    ///
272    /// Used after 3+ consecutive errors to skip aggressively to the next item.
273    fn synchronize_to_top_level(&mut self) -> Span {
274        let start = self.peek().span;
275        let mut end = start;
276        while !self.at(TokenKind::Eof) {
277            self.skip_newlines();
278            if self.at(TokenKind::Eof) || self.at_decl_start() {
279                break;
280            }
281            end = self.peek().span;
282            let _ = self.advance();
283        }
284        Span::merge(start, end)
285    }
286
287    fn parse_visibility(&mut self) -> Visibility {
288        match self.peek().kind {
289            TokenKind::Public => {
290                let _ = self.advance();
291                Visibility::Public
292            }
293            TokenKind::Internal => {
294                let _ = self.advance();
295                Visibility::Internal
296            }
297            _ => Visibility::Private,
298        }
299    }
300
301    // ─── Module declaration ───────────────────────────────────────────────────
302
303    /// Parse `module path.name NEWLINE`.
304    fn parse_module_decl(&mut self) -> ModulePath {
305        let _ = self.advance(); // consume `module`
306        let path = self.parse_module_path();
307        if self.at(TokenKind::Newline) {
308            let _ = self.advance();
309        }
310        path
311    }
312
313    /// Parse a dot-separated module path: `a.b.Name`.
314    fn parse_module_path(&mut self) -> ModulePath {
315        let start = self.peek().span;
316        let mut segments = Vec::new();
317
318        if let Some(seg) = self.try_parse_path_segment() {
319            segments.push(seg);
320        }
321
322        // Continue consuming `.segment` pairs.
323        while self.at(TokenKind::Dot) {
324            match self.peek_kind_at(1) {
325                Some(kind) if Self::is_path_segment_token(&kind) => {
326                    let _ = self.advance(); // consume `.`
327                    if let Some(seg) = self.try_parse_path_segment() {
328                        segments.push(seg);
329                    }
330                }
331                _ => break,
332            }
333        }
334
335        let end = segments.last().map(|s| s.span).unwrap_or(start);
336        ModulePath {
337            span: Span::merge(start, end),
338            segments,
339        }
340    }
341
342    /// Whether `kind` may appear as a segment of a module/import path.
343    ///
344    /// Beyond plain `Ident` / `TypeIdent`, the effect-family contextual keywords
345    /// (`effect` / `handle` / `handling`) are accepted as path segments: the spec
346    /// names the stdlib module `core.effect` (§18.3), so `effect` must be a legal
347    /// path segment even though it is a reserved keyword in item/expression
348    /// position. This is scoped to module-path / import-path parsing only — it does
349    /// not affect `effect Log { ... }` declarations or `obj.effect` field access,
350    /// which never reach this code path.
351    fn is_path_segment_token(kind: &TokenKind) -> bool {
352        matches!(
353            kind,
354            TokenKind::Ident
355                | TokenKind::TypeIdent
356                | TokenKind::Effect
357                | TokenKind::Handle
358                | TokenKind::Handling
359        )
360    }
361
362    /// Try to consume a single path segment; emit an error on failure.
363    ///
364    /// Accepts `Ident` / `TypeIdent` and the effect-family keywords (see
365    /// [`Self::is_path_segment_token`]). For keyword tokens — which carry no
366    /// `literal` text — the segment name is the keyword's textual spelling
367    /// (e.g. `effect`), taken from its [`TokenKind`] `Display`.
368    fn try_parse_path_segment(&mut self) -> Option<Ident> {
369        if Self::is_path_segment_token(&self.peek().kind) {
370            let tok = self.advance();
371            // Plain identifiers carry their text in `literal`; keyword segments
372            // (effect/handle/handling) carry none, so fall back to the keyword's
373            // textual spelling.
374            let name = tok.literal.unwrap_or_else(|| tok.kind.to_string());
375            Some(Ident {
376                name,
377                span: tok.span,
378            })
379        } else {
380            let span = self.peek().span;
381            let found = self.peek().kind.clone();
382            self.diagnostics.error(
383                DiagnosticCode {
384                    prefix: 'E',
385                    number: 2001,
386                },
387                format!("expected identifier in path, found `{found}`"),
388                span,
389            );
390            None
391        }
392    }
393
394    // ─── Import declarations ──────────────────────────────────────────────────
395
396    /// Parse `use module_path [import_list] NEWLINE`.
397    fn parse_import_decl(&mut self, vis: Visibility) -> ImportDecl {
398        let start = self.peek().span;
399        let _ = self.advance(); // consume `use`
400
401        // Parse the base module path, stopping before `.{` and `.*`.
402        let path = self.parse_import_base_path();
403
404        // Parse the optional import list.
405        let items = self.parse_import_items();
406
407        if self.at(TokenKind::Newline) {
408            let _ = self.advance();
409        }
410
411        let end = self.peek().span;
412        ImportDecl {
413            id: self.alloc_id(),
414            span: Span::merge(start, end),
415            visibility: vis,
416            path,
417            items,
418        }
419    }
420
421    /// Parse the base path in a `use` declaration.
422    ///
423    /// Greedy, but stops before `.{` and `.*` so the caller can handle import lists.
424    fn parse_import_base_path(&mut self) -> ModulePath {
425        let start = self.peek().span;
426        let mut segments = Vec::new();
427
428        if let Some(seg) = self.try_parse_path_segment() {
429            segments.push(seg);
430        }
431
432        while self.at(TokenKind::Dot) {
433            match self.peek_kind_at(1) {
434                // Stop: import list follows.
435                Some(TokenKind::LBrace) | Some(TokenKind::Star) => break,
436                // Continue consuming path segments.
437                Some(kind) if Self::is_path_segment_token(&kind) => {
438                    let _ = self.advance(); // consume `.`
439                    if let Some(seg) = self.try_parse_path_segment() {
440                        segments.push(seg);
441                    }
442                }
443                _ => break,
444            }
445        }
446
447        let end = segments.last().map(|s| s.span).unwrap_or(start);
448        ModulePath {
449            span: Span::merge(start, end),
450            segments,
451        }
452    }
453
454    /// Parse the optional import list after the base path.
455    ///
456    /// | Syntax       | Result                  |
457    /// |-------------|-------------------------|
458    /// | `.{A, B}`   | `Named([A, B])`         |
459    /// | `.*`        | `Glob`                  |
460    /// | `.Name`     | `Named([Name])`         |
461    /// | *(nothing)* | `Module`                |
462    fn parse_import_items(&mut self) -> ImportItems {
463        if !self.at(TokenKind::Dot) {
464            return ImportItems::Module;
465        }
466
467        match self.peek_kind_at(1) {
468            Some(TokenKind::Star) => {
469                let _ = self.advance(); // `.`
470                let _ = self.advance(); // `*`
471                ImportItems::Glob
472            }
473            Some(TokenKind::LBrace) => {
474                let _ = self.advance(); // `.`
475                let _ = self.advance(); // `{`
476                let names = self.parse_named_import_list();
477                let _ = self.expect(TokenKind::RBrace);
478                ImportItems::Named(names)
479            }
480            Some(TokenKind::Ident) | Some(TokenKind::TypeIdent) => {
481                let _ = self.advance(); // `.`
482                let tok = self.advance(); // the name
483                let span = tok.span;
484                let name = Ident {
485                    name: tok.literal.unwrap_or_default(),
486                    span,
487                };
488                ImportItems::Named(vec![ImportedName {
489                    span,
490                    name,
491                    alias: None,
492                }])
493            }
494            _ => ImportItems::Module,
495        }
496    }
497
498    /// Parse comma-separated names inside `{...}`.
499    fn parse_named_import_list(&mut self) -> Vec<ImportedName> {
500        let mut names = Vec::new();
501        self.skip_newlines();
502
503        while !self.at(TokenKind::RBrace) && !self.at(TokenKind::Eof) {
504            if !matches!(self.peek().kind, TokenKind::Ident | TokenKind::TypeIdent) {
505                break;
506            }
507            let tok = self.advance();
508            let start_span = tok.span;
509            let name = Ident {
510                name: tok.literal.unwrap_or_default(),
511                span: tok.span,
512            };
513
514            // Optional alias: `Name as Alias`.
515            let alias = if self.at(TokenKind::Ident) && self.peek().literal.as_deref() == Some("as")
516            {
517                let _ = self.advance(); // consume `as`
518                if matches!(self.peek().kind, TokenKind::Ident | TokenKind::TypeIdent) {
519                    let alias_tok = self.advance();
520                    Some(Ident {
521                        name: alias_tok.literal.unwrap_or_default(),
522                        span: alias_tok.span,
523                    })
524                } else {
525                    None
526                }
527            } else {
528                None
529            };
530
531            let end_span = alias.as_ref().map(|a| a.span).unwrap_or(start_span);
532            names.push(ImportedName {
533                span: Span::merge(start_span, end_span),
534                name,
535                alias,
536            });
537
538            self.skip_newlines();
539            if self.at(TokenKind::Comma) {
540                let _ = self.advance();
541                self.skip_newlines();
542            } else {
543                break;
544            }
545        }
546
547        names
548    }
549
550    // ─── Top-level items ──────────────────────────────────────────────────────
551
552    /// Parse top-level items, dispatching to declaration-specific parsers.
553    fn parse_items(&mut self) -> Vec<Item> {
554        let mut items = Vec::new();
555
556        loop {
557            self.skip_newlines();
558            if self.at(TokenKind::Eof) {
559                break;
560            }
561
562            // Skip item-level doc comments (`///`).
563            // The lexer produces DocComment tokens but the AST item types
564            // don't store them yet, so consume and discard for now.
565            while self.at(TokenKind::DocComment) || self.at(TokenKind::ModuleDocComment) {
566                let _ = self.advance();
567                self.skip_newlines();
568            }
569            if self.at(TokenKind::Eof) {
570                break;
571            }
572
573            // Collect leading annotations.
574            let mut annotations = Vec::new();
575            while self.at(TokenKind::At) {
576                annotations.push(self.parse_annotation());
577                self.skip_newlines();
578            }
579
580            // Optional visibility modifier.
581            let vis = if self.at_visibility() {
582                self.parse_visibility()
583            } else {
584                Visibility::Private
585            };
586
587            let error_count_before = self.diagnostics.error_count();
588
589            // Dispatch to the correct declaration parser.
590            let item = match self.peek().kind.clone() {
591                TokenKind::Fn | TokenKind::Async => Item::Fn(self.parse_fn_decl(annotations, vis)),
592                TokenKind::Record => Item::Record(self.parse_record_decl(annotations, vis)),
593                TokenKind::Enum => Item::Enum(self.parse_enum_decl(annotations, vis)),
594                TokenKind::Class => Item::Class(self.parse_class_decl(annotations, vis)),
595                TokenKind::Trait => Item::Trait(self.parse_trait_decl(annotations, vis, false)),
596                TokenKind::Platform => {
597                    // `platform trait Name ...`
598                    Item::PlatformTrait(self.parse_platform_trait_decl(annotations, vis))
599                }
600                TokenKind::Impl => Item::Impl(self.parse_impl_block(annotations)),
601                TokenKind::Effect => Item::Effect(self.parse_effect_decl(annotations, vis)),
602                TokenKind::Handle => Item::ModuleHandle(self.parse_module_handle_decl()),
603                TokenKind::Type => Item::TypeAlias(self.parse_type_alias_decl(annotations, vis)),
604                TokenKind::Const => Item::Const(self.parse_const_decl(annotations, vis)),
605                _ => {
606                    if self.at(TokenKind::Eof) {
607                        break;
608                    }
609                    // Unrecognized token at top level — emit error and recover.
610                    let span = self.peek().span;
611                    let found = self.peek().kind.clone();
612                    self.diagnostics.error(
613                        DiagnosticCode {
614                            prefix: 'E',
615                            number: 2050,
616                        },
617                        format!("unexpected token `{found}` at top level"),
618                        span,
619                    );
620                    self.consecutive_errors += 1;
621                    let error_span = if self.consecutive_errors >= 3 {
622                        // Panic mode: skip to next top-level item
623                        self.consecutive_errors = 0;
624                        self.synchronize_to_top_level()
625                    } else {
626                        self.synchronize()
627                    };
628                    let id = self.alloc_id();
629                    items.push(Item::Error {
630                        id,
631                        span: error_span,
632                    });
633                    continue;
634                }
635            };
636
637            // Reset consecutive error count on successful parse.
638            if self.diagnostics.error_count() == error_count_before {
639                self.consecutive_errors = 0;
640            } else {
641                self.consecutive_errors += 1;
642            }
643
644            items.push(item);
645        }
646
647        items
648    }
649
650    // ─── Annotations ─────────────────────────────────────────────────────────
651
652    /// Parse a single `@name(args)` annotation.
653    fn parse_annotation(&mut self) -> Annotation {
654        let start = self.peek().span;
655        let _ = self.advance(); // consume `@`
656
657        let name_span = self.peek().span;
658        let name = if matches!(self.peek().kind, TokenKind::Ident | TokenKind::TypeIdent) {
659            let tok = self.advance();
660            Ident {
661                name: tok.literal.unwrap_or_default(),
662                span: tok.span,
663            }
664        } else {
665            self.diagnostics.error(
666                DiagnosticCode {
667                    prefix: 'E',
668                    number: 2002,
669                },
670                format!("expected annotation name, found `{}`", self.peek().kind),
671                name_span,
672            );
673            Ident {
674                name: String::new(),
675                span: name_span,
676            }
677        };
678
679        let mut args = Vec::new();
680        if self.at(TokenKind::LParen) {
681            let _ = self.advance(); // consume `(`
682            self.skip_newlines();
683            while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
684                // Handle named arguments `key: value` — capture the label.
685                let label = if self.at(TokenKind::Ident)
686                    && self.peek_kind_at(1) == Some(TokenKind::Colon)
687                {
688                    let lbl_tok = self.advance();
689                    let _ = self.advance(); // consume `:`
690                    Some(Ident {
691                        name: lbl_tok.literal.unwrap_or_default(),
692                        span: lbl_tok.span,
693                    })
694                } else {
695                    None
696                };
697                args.push(AnnotationArg {
698                    label,
699                    value: self.parse_expr_stub(),
700                });
701                self.skip_newlines();
702                if self.at(TokenKind::Comma) {
703                    let _ = self.advance();
704                    self.skip_newlines();
705                } else {
706                    break;
707                }
708            }
709            let _ = self.expect(TokenKind::RParen);
710        }
711
712        let end = self.peek().span;
713        Annotation {
714            id: self.alloc_id(),
715            span: Span::merge(start, end),
716            name,
717            args,
718        }
719    }
720
721    // ─── Generic parameters ───────────────────────────────────────────────────
722
723    /// Parse `[T, U: Bound, ...]` generic parameter list.
724    fn parse_generic_params(&mut self) -> Vec<GenericParam> {
725        if !self.at(TokenKind::LBracket) {
726            return Vec::new();
727        }
728        let _ = self.advance(); // consume `[`
729
730        let mut params = Vec::new();
731        self.skip_newlines();
732
733        while !self.at(TokenKind::RBracket) && !self.at(TokenKind::Eof) {
734            if !matches!(self.peek().kind, TokenKind::Ident | TokenKind::TypeIdent) {
735                break;
736            }
737            let id = self.alloc_id();
738            let start = self.peek().span;
739            let tok = self.advance();
740            let name = Ident {
741                name: tok.literal.unwrap_or_default(),
742                span: tok.span,
743            };
744
745            // Optional bounds: `T: Bound1`.
746            let bounds = if self.at(TokenKind::Colon) {
747                let _ = self.advance();
748                vec![self.parse_type_path()]
749            } else {
750                Vec::new()
751            };
752
753            let end = bounds.last().map(|b| b.span).unwrap_or(name.span);
754            params.push(GenericParam {
755                id,
756                span: Span::merge(start, end),
757                name,
758                bounds,
759            });
760
761            self.skip_newlines();
762            if self.at(TokenKind::Comma) {
763                let _ = self.advance();
764                self.skip_newlines();
765            } else {
766                break;
767            }
768        }
769
770        let _ = self.expect(TokenKind::RBracket);
771        params
772    }
773
774    /// Parse `where (T: Bound, U: Bound2, ...)` constraint list.
775    fn parse_where_clause(&mut self) -> Vec<TypeConstraint> {
776        if !self.at(TokenKind::Where) {
777            return Vec::new();
778        }
779        let _ = self.advance(); // consume `where`
780
781        let _ = self.expect(TokenKind::LParen);
782        let mut constraints = Vec::new();
783        self.skip_newlines();
784
785        while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
786            if !matches!(self.peek().kind, TokenKind::Ident | TokenKind::TypeIdent) {
787                break;
788            }
789            let id = self.alloc_id();
790            let start = self.peek().span;
791            let tok = self.advance();
792            let param = Ident {
793                name: tok.literal.unwrap_or_default(),
794                span: tok.span,
795            };
796
797            let _ = self.expect(TokenKind::Colon);
798            let bounds = vec![self.parse_type_path()];
799
800            let end = bounds.last().map(|b| b.span).unwrap_or(param.span);
801            constraints.push(TypeConstraint {
802                id,
803                span: Span::merge(start, end),
804                param,
805                bounds,
806            });
807
808            self.skip_newlines();
809            if self.at(TokenKind::Comma) {
810                let _ = self.advance();
811                self.skip_newlines();
812            } else {
813                break;
814            }
815        }
816
817        let _ = self.expect(TokenKind::RParen);
818        constraints
819    }
820
821    // ─── Type expressions ─────────────────────────────────────────────────────
822
823    /// Parse a type expression.
824    fn parse_type_expr(&mut self) -> TypeExpr {
825        let id = self.alloc_id();
826        let start = self.peek().span;
827
828        let base = match self.peek().kind.clone() {
829            TokenKind::LParen => {
830                let _ = self.advance(); // consume `(`
831                self.skip_newlines();
832
833                if self.at(TokenKind::RParen) {
834                    // Unit type `()`.
835                    let end = self.advance().span;
836                    TypeExpr::Tuple {
837                        id,
838                        span: Span::merge(start, end),
839                        elems: vec![],
840                    }
841                } else {
842                    let mut elems = Vec::new();
843                    elems.push(self.parse_type_expr());
844                    self.skip_newlines();
845                    while self.at(TokenKind::Comma) {
846                        let _ = self.advance();
847                        self.skip_newlines();
848                        if self.at(TokenKind::RParen) {
849                            break;
850                        }
851                        elems.push(self.parse_type_expr());
852                        self.skip_newlines();
853                    }
854                    let end = self
855                        .expect(TokenKind::RParen)
856                        .map(|t| t.span)
857                        .unwrap_or(start);
858
859                    // Check for function type arrow.
860                    if self.at(TokenKind::ThinArrow) {
861                        let _ = self.advance();
862                        let ret = self.parse_type_expr();
863                        TypeExpr::Function {
864                            id,
865                            span: Span::merge(start, ret.span()),
866                            params: elems,
867                            ret: Box::new(ret),
868                            effects: vec![],
869                        }
870                    } else if elems.len() == 1 {
871                        // Parenthesised single type — unwrap.
872                        elems.remove(0)
873                    } else {
874                        TypeExpr::Tuple {
875                            id,
876                            span: Span::merge(start, end),
877                            elems,
878                        }
879                    }
880                }
881            }
882
883            TokenKind::SelfLower | TokenKind::SelfUpper => {
884                let tok = self.advance();
885                TypeExpr::SelfType { id, span: tok.span }
886            }
887
888            TokenKind::Ident | TokenKind::TypeIdent => {
889                // `Fn(...)` — function type using the `Fn` keyword.
890                if self.peek().literal.as_deref() == Some("Fn")
891                    && self.peek_kind_at(1) == Some(TokenKind::LParen)
892                {
893                    let _ = self.advance(); // consume `Fn`
894                    let _ = self.advance(); // consume `(`
895                    self.skip_newlines();
896                    let mut params = Vec::new();
897                    while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
898                        params.push(self.parse_type_expr());
899                        self.skip_newlines();
900                        if self.at(TokenKind::Comma) {
901                            let _ = self.advance();
902                            self.skip_newlines();
903                        } else {
904                            break;
905                        }
906                    }
907                    let _ = self.expect(TokenKind::RParen);
908                    let _ = self.expect(TokenKind::ThinArrow);
909                    let ret = self.parse_type_expr();
910                    // Optional effect clause: `with TypePath, TypePath`.
911                    let effects = self.parse_effect_clause();
912                    let end = effects
913                        .last()
914                        .map(|e: &TypePath| e.span)
915                        .unwrap_or(ret.span());
916                    TypeExpr::Function {
917                        id,
918                        span: Span::merge(start, end),
919                        params,
920                        ret: Box::new(ret),
921                        effects,
922                    }
923                } else {
924                    let path = self.parse_type_path();
925                    // Optional generic arguments `[T, U]`.
926                    let args = if self.at(TokenKind::LBracket) {
927                        let _ = self.advance();
928                        let mut args = Vec::new();
929                        self.skip_newlines();
930                        while !self.at(TokenKind::RBracket) && !self.at(TokenKind::Eof) {
931                            args.push(self.parse_type_expr());
932                            self.skip_newlines();
933                            if self.at(TokenKind::Comma) {
934                                let _ = self.advance();
935                                self.skip_newlines();
936                            } else {
937                                break;
938                            }
939                        }
940                        let _ = self.expect(TokenKind::RBracket);
941                        args
942                    } else {
943                        Vec::new()
944                    };
945                    let span = path.span;
946                    TypeExpr::Named {
947                        id,
948                        span,
949                        path,
950                        args,
951                    }
952                }
953            }
954
955            _ => {
956                self.diagnostics.error(
957                    DiagnosticCode {
958                        prefix: 'E',
959                        number: 2010,
960                    },
961                    format!("expected type expression, found `{}`", self.peek().kind),
962                    start,
963                );
964                TypeExpr::Named {
965                    id,
966                    span: start,
967                    path: TypePath {
968                        segments: vec![],
969                        span: start,
970                    },
971                    args: vec![],
972                }
973            }
974        };
975
976        // Postfix `?` for optional types.
977        if self.at(TokenKind::Question) {
978            let q = self.advance();
979            let id2 = self.alloc_id();
980            TypeExpr::Optional {
981                id: id2,
982                span: Span::merge(base.span(), q.span),
983                inner: Box::new(base),
984            }
985        } else {
986            base
987        }
988    }
989
990    /// Parse a dot-separated type path: `Std.Io.File`.
991    fn parse_type_path(&mut self) -> TypePath {
992        let start = self.peek().span;
993        let mut segments = Vec::new();
994
995        if matches!(self.peek().kind, TokenKind::Ident | TokenKind::TypeIdent) {
996            let tok = self.advance();
997            segments.push(Ident {
998                name: tok.literal.unwrap_or_default(),
999                span: tok.span,
1000            });
1001        }
1002
1003        while self.at(TokenKind::Dot) {
1004            match self.peek_kind_at(1) {
1005                Some(TokenKind::TypeIdent) | Some(TokenKind::Ident) => {
1006                    let _ = self.advance(); // consume `.`
1007                    let tok = self.advance();
1008                    segments.push(Ident {
1009                        name: tok.literal.unwrap_or_default(),
1010                        span: tok.span,
1011                    });
1012                }
1013                _ => break,
1014            }
1015        }
1016
1017        let end = segments.last().map(|s| s.span).unwrap_or(start);
1018        TypePath {
1019            segments,
1020            span: Span::merge(start, end),
1021        }
1022    }
1023
1024    // ─── Expression parsing (Pratt / precedence climbing) ────────────────────
1025
1026    /// Parse a full expression.
1027    pub(crate) fn parse_expr(&mut self) -> Expr {
1028        self.parse_prec(0)
1029    }
1030
1031    /// Kept as an alias so all existing call-sites continue to work.
1032    fn parse_expr_stub(&mut self) -> Expr {
1033        self.parse_expr()
1034    }
1035
1036    /// Precedence climbing: parse an expression whose top-level binary operator
1037    /// has precedence ≥ `min_prec`.
1038    fn parse_prec(&mut self, min_prec: u8) -> Expr {
1039        // Parse the left-hand side (unary + postfix).
1040        let mut left = self.parse_unary();
1041
1042        // Track whether we've consumed a comparison operator at this level.
1043        // Comparisons are non-associative (`a == b == c` is rejected), but
1044        // lower-precedence operators like `&&`/`||` must still be accepted
1045        // after a comparison (e.g. `a == 0 && b == 0`).
1046        let mut seen_comparison = false;
1047
1048        loop {
1049            // Continuation rule: if the current token is a newline and the next
1050            // non-newline token is `|>` (Pipe), allow the expression to continue
1051            // on the next line.
1052            if self.at(TokenKind::Newline) {
1053                match self.peek_past_newlines_kind() {
1054                    Some(TokenKind::Pipe) => self.skip_newlines(),
1055                    _ => break,
1056                }
1057            }
1058
1059            let Some((op_prec, right_prec, op_tok)) = self.binary_op_info() else {
1060                break;
1061            };
1062            if op_prec < min_prec {
1063                break;
1064            }
1065
1066            // Non-associative comparison: reject a second comparison at this level.
1067            if op_prec == 7 {
1068                if seen_comparison {
1069                    break;
1070                }
1071                seen_comparison = true;
1072            }
1073
1074            // Special case: `is` — RHS is a type expression, not a value expression.
1075            if op_tok == OpTag::Is {
1076                let _ = self.advance(); // consume `is`
1077                self.skip_newlines(); // allow RHS on next line after operator
1078                let ty = self.parse_type_expr();
1079                let id = self.alloc_id();
1080                let span = Span::merge(left.span(), ty.span());
1081                left = Expr::Is {
1082                    id,
1083                    span,
1084                    expr: Box::new(left),
1085                    type_expr: ty,
1086                };
1087                continue; // non-associativity handled by seen_comparison flag
1088            }
1089
1090            // Special case: range operators (non-associative).
1091            if matches!(op_tok, OpTag::Range | OpTag::RangeInclusive) {
1092                let inclusive = op_tok == OpTag::RangeInclusive;
1093                let _ = self.advance(); // consume `..` or `..=`
1094                self.skip_newlines(); // allow RHS on next line after operator
1095                let right = self.parse_prec(op_prec + 1);
1096                let id = self.alloc_id();
1097                let span = Span::merge(left.span(), right.span());
1098                left = Expr::Range {
1099                    id,
1100                    span,
1101                    lo: Box::new(left),
1102                    hi: Box::new(right),
1103                    inclusive,
1104                };
1105                break; // non-associative
1106            }
1107
1108            let _ = self.advance(); // consume the operator
1109                                    // Continuation rule: operator at end of line — skip newlines before RHS.
1110            self.skip_newlines();
1111
1112            let right = self.parse_prec(right_prec);
1113            let id = self.alloc_id();
1114            let span = Span::merge(left.span(), right.span());
1115
1116            left = match op_tok {
1117                OpTag::Assign(op) => Expr::Assign {
1118                    id,
1119                    span,
1120                    op,
1121                    target: Box::new(left),
1122                    value: Box::new(right),
1123                },
1124                OpTag::Pipe => Expr::Pipe {
1125                    id,
1126                    span,
1127                    left: Box::new(left),
1128                    right: Box::new(right),
1129                },
1130                OpTag::Compose => Expr::Compose {
1131                    id,
1132                    span,
1133                    left: Box::new(left),
1134                    right: Box::new(right),
1135                },
1136                OpTag::Binary(op) => Expr::Binary {
1137                    id,
1138                    span,
1139                    op,
1140                    left: Box::new(left),
1141                    right: Box::new(right),
1142                },
1143                OpTag::Is | OpTag::Range | OpTag::RangeInclusive => unreachable!(),
1144            };
1145
1146            // Non-associativity for comparisons is handled by the
1147            // seen_comparison flag at the top of the loop.
1148        }
1149
1150        left
1151    }
1152
1153    /// Returns `(op_precedence, right_min_precedence, op_tag)` for the current token,
1154    /// or `None` if it's not a binary operator.
1155    fn binary_op_info(&self) -> Option<(u8, u8, OpTag)> {
1156        // Precedence levels (15 total):
1157        //  1 = Assignment (right-assoc)
1158        //  2 = Pipe (left-assoc)
1159        //  3 = Compose >> (left-assoc)
1160        //  4 = Range .. ..= (non-assoc)
1161        //  5 = Or ||
1162        //  6 = And &&
1163        //  7 = Compare == != < > <= >= is (non-assoc)
1164        //  8 = BitOr |
1165        //  9 = BitXor ^
1166        // 10 = BitAnd &
1167        // 11 = Add + -
1168        // 12 = Mul * / %
1169        // 13 = Power ** (right-assoc)
1170        // 14 = Unary (prefix)
1171        // 15 = Postfix (call, index, member)
1172        let kind = &self.peek().kind;
1173        let (prec, right_prec, tag) = match kind {
1174            // Assignment — right-associative → right_prec = same level
1175            TokenKind::Assign => (1, 1, OpTag::Assign(AssignOp::Assign)),
1176            TokenKind::PlusEq => (1, 1, OpTag::Assign(AssignOp::AddAssign)),
1177            TokenKind::MinusEq => (1, 1, OpTag::Assign(AssignOp::SubAssign)),
1178            TokenKind::StarEq => (1, 1, OpTag::Assign(AssignOp::MulAssign)),
1179            TokenKind::SlashEq => (1, 1, OpTag::Assign(AssignOp::DivAssign)),
1180            TokenKind::PercentEq => (1, 1, OpTag::Assign(AssignOp::RemAssign)),
1181            // Pipe — left-assoc
1182            TokenKind::Pipe => (2, 3, OpTag::Pipe),
1183            // Compose (`>>` / Shr token) — left-assoc
1184            TokenKind::Shr | TokenKind::Compose => (3, 4, OpTag::Compose),
1185            // Range — non-associative
1186            TokenKind::DotDot => (4, 5, OpTag::Range),
1187            TokenKind::DotDotEq => (4, 5, OpTag::RangeInclusive),
1188            // Logical or
1189            TokenKind::Or => (5, 6, OpTag::Binary(BinOp::Or)),
1190            // Logical and
1191            TokenKind::And => (6, 7, OpTag::Binary(BinOp::And)),
1192            // Comparison — non-associative (right_prec > prec forces stop after one)
1193            TokenKind::Eq => (7, 8, OpTag::Binary(BinOp::Eq)),
1194            TokenKind::Neq => (7, 8, OpTag::Binary(BinOp::Ne)),
1195            TokenKind::Lt => (7, 8, OpTag::Binary(BinOp::Lt)),
1196            TokenKind::Gt => (7, 8, OpTag::Binary(BinOp::Gt)),
1197            TokenKind::Lte => (7, 8, OpTag::Binary(BinOp::Le)),
1198            TokenKind::Gte => (7, 8, OpTag::Binary(BinOp::Ge)),
1199            TokenKind::Is => (7, 8, OpTag::Is),
1200            // Bitwise
1201            TokenKind::BitOr => (8, 9, OpTag::Binary(BinOp::BitOr)),
1202            TokenKind::BitXor => (9, 10, OpTag::Binary(BinOp::BitXor)),
1203            TokenKind::BitAnd => (10, 11, OpTag::Binary(BinOp::BitAnd)),
1204            // Add / Sub
1205            TokenKind::Plus => (11, 12, OpTag::Binary(BinOp::Add)),
1206            TokenKind::Minus => (11, 12, OpTag::Binary(BinOp::Sub)),
1207            // Mul / Div / Rem
1208            TokenKind::Star => (12, 13, OpTag::Binary(BinOp::Mul)),
1209            TokenKind::Slash => (12, 13, OpTag::Binary(BinOp::Div)),
1210            TokenKind::Percent => (12, 13, OpTag::Binary(BinOp::Rem)),
1211            // Power — right-associative
1212            TokenKind::Power => (13, 13, OpTag::Binary(BinOp::Pow)),
1213            _ => return None,
1214        };
1215        Some((prec, right_prec, tag))
1216    }
1217
1218    /// Parse unary prefix operators, then delegate to postfix chain.
1219    fn parse_unary(&mut self) -> Expr {
1220        let id = self.alloc_id();
1221        let span = self.peek().span;
1222
1223        match self.peek().kind.clone() {
1224            TokenKind::Minus => {
1225                let _ = self.advance();
1226                let operand = self.parse_unary();
1227                let span = Span::merge(span, operand.span());
1228                Expr::Unary {
1229                    id,
1230                    span,
1231                    op: UnaryOp::Neg,
1232                    operand: Box::new(operand),
1233                }
1234            }
1235            TokenKind::Not => {
1236                let _ = self.advance();
1237                let operand = self.parse_unary();
1238                let span = Span::merge(span, operand.span());
1239                Expr::Unary {
1240                    id,
1241                    span,
1242                    op: UnaryOp::Not,
1243                    operand: Box::new(operand),
1244                }
1245            }
1246            TokenKind::BitNot => {
1247                let _ = self.advance();
1248                let operand = self.parse_unary();
1249                let span = Span::merge(span, operand.span());
1250                Expr::Unary {
1251                    id,
1252                    span,
1253                    op: UnaryOp::BitNot,
1254                    operand: Box::new(operand),
1255                }
1256            }
1257            _ => self.parse_postfix(),
1258        }
1259    }
1260
1261    /// Parse a primary expression then apply postfix operators in a loop.
1262    fn parse_postfix(&mut self) -> Expr {
1263        let mut expr = self.parse_primary();
1264
1265        loop {
1266            // Continuation rule: next line starts with `.` → allow method/field chaining
1267            // across lines (e.g., `expr\n  .method()`).
1268            if self.at(TokenKind::Newline) {
1269                match self.peek_past_newlines_kind() {
1270                    Some(TokenKind::Dot) => self.skip_newlines(),
1271                    _ => break,
1272                }
1273            }
1274
1275            match self.peek().kind.clone() {
1276                // `?` — error propagation
1277                TokenKind::Question => {
1278                    let end_span = self.advance().span;
1279                    let id = self.alloc_id();
1280                    let span = Span::merge(expr.span(), end_span);
1281                    expr = Expr::Try {
1282                        id,
1283                        span,
1284                        expr: Box::new(expr),
1285                    };
1286                }
1287                // `.` — field access, method call, or `.await`
1288                TokenKind::Dot => {
1289                    match self.peek_kind_at(1) {
1290                        Some(TokenKind::Await) => {
1291                            let _ = self.advance(); // consume `.`
1292                            let end_span = self.advance().span; // consume `await`
1293                            let id = self.alloc_id();
1294                            let span = Span::merge(expr.span(), end_span);
1295                            expr = Expr::Await {
1296                                id,
1297                                span,
1298                                expr: Box::new(expr),
1299                            };
1300                        }
1301                        Some(TokenKind::Ident) | Some(TokenKind::TypeIdent) => {
1302                            let _ = self.advance(); // consume `.`
1303                            let tok = self.advance(); // consume field/method name
1304                            let field = Ident {
1305                                name: tok.literal.unwrap_or_default(),
1306                                span: tok.span,
1307                            };
1308                            // Check if followed by `(` → method call (possibly with `[type_args]`)
1309                            let type_args = self.parse_optional_type_args();
1310                            if self.at(TokenKind::LParen) {
1311                                let _ = self.advance(); // consume `(`
1312                                let args = self.parse_arg_list();
1313                                let _ = self.expect(TokenKind::RParen);
1314                                let id = self.alloc_id();
1315                                let span = Span::merge(expr.span(), self.peek().span);
1316                                expr = Expr::MethodCall {
1317                                    id,
1318                                    span,
1319                                    receiver: Box::new(expr),
1320                                    method: field,
1321                                    type_args,
1322                                    args,
1323                                };
1324                            } else {
1325                                let id = self.alloc_id();
1326                                let span = Span::merge(expr.span(), field.span);
1327                                expr = Expr::FieldAccess {
1328                                    id,
1329                                    span,
1330                                    object: Box::new(expr),
1331                                    field,
1332                                };
1333                            }
1334                        }
1335                        // `t.0` — tuple positional indexing. The lexer tokenizes
1336                        // this as `Ident`/expr, `Dot`, `IntLiteral` (or
1337                        // `FloatLiteral` for chains like `t.0.0`). Positional
1338                        // tuple indexing is deferred to v1.x
1339                        // (spec §7.6); emit a specific, actionable diagnostic
1340                        // pointing users at destructuring rather than the
1341                        // generic "expected expression, found `.`" error, and
1342                        // recover by consuming the `.` and the numeric literal.
1343                        Some(TokenKind::IntLiteral) | Some(TokenKind::FloatLiteral) => {
1344                            let dot_span = self.advance().span; // consume `.`
1345                            let index_tok = self.advance(); // consume the numeric literal
1346                            let index = index_tok.literal.clone().unwrap_or_default();
1347                            let span = Span::merge(dot_span, index_tok.span);
1348                            self.diagnostics.error(
1349                                DiagnosticCode {
1350                                    prefix: 'E',
1351                                    number: 2092,
1352                                },
1353                                format!(
1354                                    "tuple positional indexing `t.{index}` is not available in v1; \
1355                                     destructure with `let (a, b) = t` to bind tuple elements instead"
1356                                ),
1357                                span,
1358                            );
1359                            // `expr` is left unchanged so the surrounding
1360                            // expression still has a node to attach to; parsing
1361                            // continues from the next token.
1362                        }
1363                        _ => break,
1364                    }
1365                }
1366                // `(` — function call
1367                TokenKind::LParen => {
1368                    let _ = self.advance(); // consume `(`
1369                    let type_args = Vec::new(); // type args parsed before `(` via `[...]`
1370                    let args = self.parse_arg_list();
1371                    let end_span = self
1372                        .expect(TokenKind::RParen)
1373                        .map(|t| t.span)
1374                        .unwrap_or_else(|_| self.peek().span);
1375                    let id = self.alloc_id();
1376                    let span = Span::merge(expr.span(), end_span);
1377                    expr = Expr::Call {
1378                        id,
1379                        span,
1380                        callee: Box::new(expr),
1381                        args,
1382                        type_args,
1383                    };
1384                }
1385                // `[` — index access, or type application on a type name
1386                TokenKind::LBracket => {
1387                    // Detect `TypeName[TypeArgs].method(...)` — e.g.,
1388                    // `Channel[String].new()`. The brackets hold type
1389                    // arguments which the interpreter can discard (dispatch
1390                    // is by qualified name), so consume and continue the
1391                    // postfix loop so `.method(...)` parses normally.
1392                    if Self::expr_is_simple_type_name(&expr) && self.is_type_args_before_dot() {
1393                        let _ = self.advance(); // consume `[`
1394                        self.skip_newlines();
1395                        while !self.at(TokenKind::RBracket) && !self.at(TokenKind::Eof) {
1396                            let _ = self.parse_type_expr();
1397                            self.skip_newlines();
1398                            if self.at(TokenKind::Comma) {
1399                                let _ = self.advance();
1400                                self.skip_newlines();
1401                            } else {
1402                                break;
1403                            }
1404                        }
1405                        let _ = self.expect(TokenKind::RBracket);
1406                        continue;
1407                    }
1408                    let _ = self.advance(); // consume `[`
1409                    let index = self.parse_expr();
1410                    let end_span = self
1411                        .expect(TokenKind::RBracket)
1412                        .map(|t| t.span)
1413                        .unwrap_or_else(|_| self.peek().span);
1414                    let id = self.alloc_id();
1415                    let span = Span::merge(expr.span(), end_span);
1416                    expr = Expr::Index {
1417                        id,
1418                        span,
1419                        object: Box::new(expr),
1420                        index: Box::new(index),
1421                    };
1422                }
1423                // `{` after TypeIdent — record construction
1424                TokenKind::LBrace if self.expr_is_type_path(&expr) => {
1425                    let path = self.expr_to_type_path(&expr);
1426                    let record = self.parse_record_construct(expr.span(), path);
1427                    expr = record;
1428                }
1429                _ => break,
1430            }
1431        }
1432
1433        expr
1434    }
1435
1436    /// Returns `true` if `expr` is a type path suitable for record construction.
1437    ///
1438    /// Handles both simple `TypeIdent` and module-qualified paths like `Mod.Type`
1439    /// where all segments start with uppercase.
1440    ///
1441    /// All-uppercase names like `MAX` or `LIMIT` are treated as constants, not
1442    /// type paths, so that `for i in 1..=MAX { … }` does not misparse `{` as
1443    /// record construction.
1444    fn expr_is_type_path(&self, expr: &Expr) -> bool {
1445        match expr {
1446            Expr::Identifier { name, .. } => Self::is_type_name(&name.name),
1447            Expr::FieldAccess { object, field, .. } => {
1448                Self::is_type_name(&field.name) && self.expr_is_type_path(object)
1449            }
1450            _ => false,
1451        }
1452    }
1453
1454    /// Returns `true` if `name` looks like a PascalCase type name rather than
1455    /// an UPPER_CASE constant.  A type name starts with uppercase and either
1456    /// is a single character (`T`, `A`) or contains at least one lowercase
1457    /// letter (`Point`, `MyType`).  Multi-character all-uppercase names like
1458    /// `MAX` or `LIMIT` are treated as constants.
1459    fn is_type_name(name: &str) -> bool {
1460        name.starts_with(|c: char| c.is_uppercase())
1461            && (name.len() == 1 || name.contains(|c: char| c.is_lowercase()))
1462    }
1463
1464    /// Returns `true` if `expr` is a single identifier whose name looks like
1465    /// a type (PascalCase). Used to disambiguate `Type[Args].method(...)` from
1466    /// plain index access.
1467    fn expr_is_simple_type_name(expr: &Expr) -> bool {
1468        matches!(expr, Expr::Identifier { name, .. } if Self::is_type_name(&name.name))
1469    }
1470
1471    /// Peek ahead to detect `[TypeIdent (, TypeIdent)*] . Ident` pattern —
1472    /// i.e., a type argument list immediately followed by a method access.
1473    /// Used to parse `Channel[String].new()` as `Channel.new()` with type
1474    /// arguments consumed and discarded.
1475    fn is_type_args_before_dot(&self) -> bool {
1476        let mut offset = 1; // skip past `[`
1477        loop {
1478            while self.peek_kind_at(offset) == Some(TokenKind::Newline) {
1479                offset += 1;
1480            }
1481            match self.peek_kind_at(offset) {
1482                Some(TokenKind::TypeIdent) => offset += 1,
1483                _ => return false,
1484            }
1485            while self.peek_kind_at(offset) == Some(TokenKind::Newline) {
1486                offset += 1;
1487            }
1488            match self.peek_kind_at(offset) {
1489                Some(TokenKind::Comma) => {
1490                    offset += 1;
1491                }
1492                Some(TokenKind::RBracket) => {
1493                    offset += 1;
1494                    if self.peek_kind_at(offset) != Some(TokenKind::Dot) {
1495                        return false;
1496                    }
1497                    offset += 1;
1498                    return matches!(
1499                        self.peek_kind_at(offset),
1500                        Some(TokenKind::Ident) | Some(TokenKind::TypeIdent)
1501                    );
1502                }
1503                _ => return false,
1504            }
1505        }
1506    }
1507
1508    /// Convert an expression (Identifier or FieldAccess chain) to a [`TypePath`].
1509    fn expr_to_type_path(&self, expr: &Expr) -> TypePath {
1510        match expr {
1511            Expr::Identifier { name, span, .. } => TypePath {
1512                segments: vec![name.clone()],
1513                span: *span,
1514            },
1515            Expr::FieldAccess {
1516                object,
1517                field,
1518                span,
1519                ..
1520            } => {
1521                let mut path = self.expr_to_type_path(object);
1522                path.segments.push(field.clone());
1523                path.span = *span;
1524                path
1525            }
1526            _ => TypePath {
1527                segments: vec![],
1528                span: expr.span(),
1529            },
1530        }
1531    }
1532
1533    /// Parse `[TypeArg, ...]` optional type argument list before `(`.
1534    fn parse_optional_type_args(&mut self) -> Vec<TypeExpr> {
1535        if self.at(TokenKind::LBracket) && !self.is_index_bracket() {
1536            let _ = self.advance(); // consume `[`
1537            let mut args = Vec::new();
1538            self.skip_newlines();
1539            while !self.at(TokenKind::RBracket) && !self.at(TokenKind::Eof) {
1540                args.push(self.parse_type_expr());
1541                self.skip_newlines();
1542                if self.at(TokenKind::Comma) {
1543                    let _ = self.advance();
1544                    self.skip_newlines();
1545                } else {
1546                    break;
1547                }
1548            }
1549            let _ = self.expect(TokenKind::RBracket);
1550            args
1551        } else {
1552            Vec::new()
1553        }
1554    }
1555
1556    /// Heuristic: is the `[` an index access rather than type argument list?
1557    ///
1558    /// Returns `false` (i.e. "this is type args") when the bracket contains only
1559    /// comma-separated `TypeIdent` tokens and is immediately followed by `(`.
1560    /// Otherwise returns `true` (treat as index access).
1561    fn is_index_bracket(&self) -> bool {
1562        let mut offset = 1; // skip past `[`
1563
1564        loop {
1565            // Skip newlines
1566            while self.peek_kind_at(offset) == Some(TokenKind::Newline) {
1567                offset += 1;
1568            }
1569
1570            // Expect a TypeIdent (uppercase identifier)
1571            match self.peek_kind_at(offset) {
1572                Some(TokenKind::TypeIdent) => offset += 1,
1573                _ => return true, // not a type arg list
1574            }
1575
1576            // Skip newlines
1577            while self.peek_kind_at(offset) == Some(TokenKind::Newline) {
1578                offset += 1;
1579            }
1580
1581            // Expect `,` (more type args) or `]` (end of list)
1582            match self.peek_kind_at(offset) {
1583                Some(TokenKind::Comma) => offset += 1,
1584                Some(TokenKind::RBracket) => {
1585                    offset += 1;
1586                    // Type args when followed by `(`
1587                    return !matches!(self.peek_kind_at(offset), Some(TokenKind::LParen));
1588                }
1589                _ => return true,
1590            }
1591        }
1592    }
1593
1594    /// Parse a comma-separated argument list (inside already-consumed `(`).
1595    fn parse_arg_list(&mut self) -> Vec<Arg> {
1596        let mut args = Vec::new();
1597        self.skip_newlines();
1598
1599        while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
1600            let start = self.peek().span;
1601
1602            // Check for `mut` prefix on argument
1603            let mutable = if self.at(TokenKind::Mut) {
1604                let _ = self.advance();
1605                true
1606            } else {
1607                false
1608            };
1609
1610            // Check for labeled argument: `label: expr`
1611            let (label, value) =
1612                if self.at(TokenKind::Ident) && self.peek_kind_at(1) == Some(TokenKind::Colon) {
1613                    let tok = self.advance();
1614                    let label = Ident {
1615                        name: tok.literal.unwrap_or_default(),
1616                        span: tok.span,
1617                    };
1618                    let _ = self.advance(); // consume `:`
1619                    let value = self.parse_expr();
1620                    (Some(label), value)
1621                } else {
1622                    let value = self.parse_expr();
1623                    (None, value)
1624                };
1625
1626            let end = value.span();
1627            args.push(Arg {
1628                span: Span::merge(start, end),
1629                label,
1630                mutable,
1631                value,
1632            });
1633
1634            self.skip_newlines();
1635            if self.at(TokenKind::Comma) {
1636                let _ = self.advance();
1637                self.skip_newlines();
1638            } else {
1639                break;
1640            }
1641        }
1642
1643        args
1644    }
1645
1646    /// Parse record construction: `Type { field: val, name, ..spread }`.
1647    fn parse_record_construct(&mut self, start: Span, path: TypePath) -> Expr {
1648        let _ = self.advance(); // consume `{`
1649        let mut fields = Vec::new();
1650        let mut spread = None;
1651
1652        self.skip_newlines();
1653        while !self.at(TokenKind::RBrace) && !self.at(TokenKind::Eof) {
1654            // `..expr` — spread
1655            if self.at(TokenKind::DotDot) {
1656                let spread_start = self.advance().span; // consume `..`
1657                let expr = self.parse_expr();
1658                let span = Span::merge(spread_start, expr.span());
1659                spread = Some(Box::new(RecordSpread { span, expr }));
1660                self.skip_newlines();
1661                break;
1662            }
1663
1664            // `name: expr` or shorthand `name`
1665            let field_span = self.peek().span;
1666            if !self.at(TokenKind::Ident) {
1667                break;
1668            }
1669            let tok = self.advance();
1670            let name = Ident {
1671                name: tok.literal.unwrap_or_default(),
1672                span: tok.span,
1673            };
1674
1675            let value = if self.at(TokenKind::Colon) {
1676                let _ = self.advance(); // consume `:`
1677                Some(self.parse_expr())
1678            } else {
1679                None // shorthand
1680            };
1681
1682            let field_end = value.as_ref().map(|v| v.span()).unwrap_or(field_span);
1683            fields.push(RecordField {
1684                span: Span::merge(field_span, field_end),
1685                name,
1686                value,
1687            });
1688
1689            self.skip_newlines();
1690            if self.at(TokenKind::Comma) {
1691                let _ = self.advance();
1692                self.skip_newlines();
1693            }
1694        }
1695
1696        let end_span = self
1697            .expect(TokenKind::RBrace)
1698            .map(|t| t.span)
1699            .unwrap_or(start);
1700        let id = self.alloc_id();
1701        Expr::RecordConstruct {
1702            id,
1703            span: Span::merge(start, end_span),
1704            path,
1705            fields,
1706            spread,
1707        }
1708    }
1709
1710    /// Convert a `TypeExpr` to an `Expr` (used for the RHS of `is`).
1711    /// Parse a primary (atom) expression.
1712    fn parse_primary(&mut self) -> Expr {
1713        let id = self.alloc_id();
1714        let span = self.peek().span;
1715
1716        match self.peek().kind.clone() {
1717            // ── Literals ──────────────────────────────────────────────────────
1718            TokenKind::IntLiteral => {
1719                let tok = self.advance();
1720                Expr::Literal {
1721                    id,
1722                    span,
1723                    lit: Literal::Int(tok.literal.unwrap_or_default()),
1724                }
1725            }
1726            TokenKind::FloatLiteral => {
1727                let tok = self.advance();
1728                Expr::Literal {
1729                    id,
1730                    span,
1731                    lit: Literal::Float(tok.literal.unwrap_or_default()),
1732                }
1733            }
1734            TokenKind::StringLiteral
1735            | TokenKind::RawStringLiteral
1736            | TokenKind::MultiLineStringLiteral
1737            | TokenKind::RawMultiLineStringLiteral => {
1738                let tok = self.advance();
1739                Expr::Literal {
1740                    id,
1741                    span,
1742                    lit: Literal::String(tok.literal.unwrap_or_default()),
1743                }
1744            }
1745            TokenKind::BoolLiteral => {
1746                let tok = self.advance();
1747                // The lexer stores no literal text for BoolLiteral; check source text.
1748                let value = self.source.slice(tok.span) == "true";
1749                Expr::Literal {
1750                    id,
1751                    span,
1752                    lit: Literal::Bool(value),
1753                }
1754            }
1755            TokenKind::CharLiteral => {
1756                let tok = self.advance();
1757                Expr::Literal {
1758                    id,
1759                    span,
1760                    lit: Literal::Char(tok.literal.unwrap_or_default()),
1761                }
1762            }
1763            // ── String interpolation ──────────────────────────────────────────
1764            TokenKind::StringLiteralPart | TokenKind::InterpolationStart => {
1765                self.parse_interpolation(id, span)
1766            }
1767            // ── Identifiers ───────────────────────────────────────────────────
1768            TokenKind::Ident => {
1769                let tok = self.advance();
1770                let name = Ident {
1771                    name: tok.literal.unwrap_or_default(),
1772                    span: tok.span,
1773                };
1774                Expr::Identifier {
1775                    id,
1776                    span: tok.span,
1777                    name,
1778                }
1779            }
1780            TokenKind::TypeIdent
1781            | TokenKind::Ok_
1782            | TokenKind::Err_
1783            | TokenKind::Some_
1784            | TokenKind::None_ => {
1785                let tok = self.advance();
1786                // Use display name for keyword variants (Ok_, Err_ → "Ok", "Err").
1787                let name = Ident {
1788                    name: tok.literal.unwrap_or_else(|| tok.kind.to_string()),
1789                    span: tok.span,
1790                };
1791                Expr::Identifier {
1792                    id,
1793                    span: tok.span,
1794                    name,
1795                }
1796            }
1797            TokenKind::SelfLower => {
1798                let tok = self.advance();
1799                let name = Ident {
1800                    name: "self".into(),
1801                    span: tok.span,
1802                };
1803                Expr::Identifier {
1804                    id,
1805                    span: tok.span,
1806                    name,
1807                }
1808            }
1809            TokenKind::SelfUpper => {
1810                let tok = self.advance();
1811                let name = Ident {
1812                    name: "Self".into(),
1813                    span: tok.span,
1814                };
1815                Expr::Identifier {
1816                    id,
1817                    span: tok.span,
1818                    name,
1819                }
1820            }
1821            // ── Placeholder `_` ───────────────────────────────────────────────
1822            TokenKind::Underscore => {
1823                let _ = self.advance();
1824                Expr::Placeholder { id, span }
1825            }
1826            // ── `unreachable` ─────────────────────────────────────────────────
1827            TokenKind::Unreachable => {
1828                let _ = self.advance();
1829                // Consume optional trailing `()` so `unreachable()` works
1830                if self.at(TokenKind::LParen) {
1831                    if let Some(next) = self.tokens.get(self.pos + 1) {
1832                        if next.kind == TokenKind::RParen {
1833                            let _ = self.advance(); // (
1834                            let _ = self.advance(); // )
1835                        }
1836                    }
1837                }
1838                Expr::Unreachable { id, span }
1839            }
1840            // ── `return` ──────────────────────────────────────────────────────
1841            TokenKind::Return => {
1842                let _ = self.advance();
1843                let value = if !self.at_stmt_terminator() {
1844                    Some(Box::new(self.parse_expr()))
1845                } else {
1846                    None
1847                };
1848                let end = value.as_ref().map(|v| v.span()).unwrap_or(span);
1849                Expr::Return {
1850                    id,
1851                    span: Span::merge(span, end),
1852                    value,
1853                }
1854            }
1855            // ── `break` ───────────────────────────────────────────────────────
1856            TokenKind::Break => {
1857                let _ = self.advance();
1858                let value = if !self.at_stmt_terminator() {
1859                    Some(Box::new(self.parse_expr()))
1860                } else {
1861                    None
1862                };
1863                let end = value.as_ref().map(|v| v.span()).unwrap_or(span);
1864                Expr::Break {
1865                    id,
1866                    span: Span::merge(span, end),
1867                    value,
1868                }
1869            }
1870            // ── `continue` ────────────────────────────────────────────────────
1871            TokenKind::Continue => {
1872                let _ = self.advance();
1873                Expr::Continue { id, span }
1874            }
1875            // ── `await expr` (prefix form) ────────────────────────────────────
1876            TokenKind::Await => {
1877                let _ = self.advance();
1878                let inner = self.parse_unary();
1879                let end = inner.span();
1880                Expr::Await {
1881                    id,
1882                    span: Span::merge(span, end),
1883                    expr: Box::new(inner),
1884                }
1885            }
1886            // ── `if` expression ───────────────────────────────────────────────
1887            TokenKind::If => self.parse_if_expr(),
1888            // ── `match` expression ────────────────────────────────────────────
1889            TokenKind::Match => self.parse_match_expr(),
1890            // ── `loop` expression ─────────────────────────────────────────────
1891            TokenKind::Loop => self.parse_loop_expr(),
1892            // ── Parenthesised / tuple / lambda ────────────────────────────────
1893            TokenKind::LParen => {
1894                if self.is_lambda_start() {
1895                    self.parse_lambda()
1896                } else {
1897                    self.parse_paren_or_tuple()
1898                }
1899            }
1900            // ── List literal `[...]` ──────────────────────────────────────────
1901            TokenKind::LBracket => self.parse_list_literal(),
1902            // ── Set literal `#{...}` ──────────────────────────────────────────
1903            TokenKind::Hash => {
1904                if self.peek_kind_at(1) == Some(TokenKind::LBrace) {
1905                    self.parse_set_literal()
1906                } else {
1907                    // Stray `#` — skip and return error expr
1908                    let _ = self.advance();
1909                    self.diagnostics.error(
1910                        DiagnosticCode {
1911                            prefix: 'E',
1912                            number: 2022,
1913                        },
1914                        "expected `{` after `#` for set literal".to_string(),
1915                        span,
1916                    );
1917                    Expr::Literal {
1918                        id,
1919                        span,
1920                        lit: Literal::Unit,
1921                    }
1922                }
1923            }
1924            // ── Block or map literal `{...}` ──────────────────────────────────
1925            TokenKind::LBrace => {
1926                if self.is_map_literal_start() {
1927                    self.parse_map_literal()
1928                } else {
1929                    let block = self.parse_block();
1930                    let block_span = block.span;
1931                    Expr::Block {
1932                        id: self.alloc_id(),
1933                        span: block_span,
1934                        block,
1935                    }
1936                }
1937            }
1938            _ => {
1939                self.diagnostics.error(
1940                    DiagnosticCode {
1941                        prefix: 'E',
1942                        number: 2020,
1943                    },
1944                    format!("expected expression, found `{}`", self.peek().kind),
1945                    span,
1946                );
1947                // Skip the offending token to avoid infinite loops
1948                if !self.at(TokenKind::Eof) {
1949                    let _ = self.advance();
1950                }
1951                Expr::Literal {
1952                    id,
1953                    span,
1954                    lit: Literal::Unit,
1955                }
1956            }
1957        }
1958    }
1959
1960    /// Parse string interpolation: sequences of `StringLiteralPart` and `${expr}`.
1961    fn parse_interpolation(&mut self, id: NodeId, span: Span) -> Expr {
1962        let mut parts = Vec::new();
1963        let mut end = span;
1964
1965        loop {
1966            match self.peek().kind.clone() {
1967                TokenKind::StringLiteralPart => {
1968                    let tok = self.advance();
1969                    end = tok.span;
1970                    parts.push(InterpolationPart::Literal(tok.literal.unwrap_or_default()));
1971                }
1972                TokenKind::InterpolationStart => {
1973                    let _ = self.advance(); // consume `${`
1974                    let expr = self.parse_expr();
1975                    end = expr.span();
1976                    parts.push(InterpolationPart::Expr(expr));
1977                    // expect `}` (InterpolationEnd)
1978                    if self.at(TokenKind::InterpolationEnd) || self.at(TokenKind::RBrace) {
1979                        end = self.advance().span;
1980                    }
1981                }
1982                _ => break,
1983            }
1984        }
1985
1986        Expr::Interpolation {
1987            id,
1988            span: Span::merge(span, end),
1989            parts,
1990        }
1991    }
1992
1993    /// Lookahead: does `(...)` look like a lambda parameter list followed by `=>`?
1994    fn is_lambda_start(&self) -> bool {
1995        // We're pointing at `(`. Find the matching `)` then check for `=>`.
1996        let mut i = self.pos + 1; // skip `(`
1997        let mut depth = 1usize;
1998
1999        while i < self.tokens.len() {
2000            match &self.tokens[i].kind {
2001                TokenKind::LParen => depth += 1,
2002                TokenKind::RParen => {
2003                    depth -= 1;
2004                    if depth == 0 {
2005                        i += 1;
2006                        break;
2007                    }
2008                }
2009                TokenKind::Eof => return false,
2010                _ => {}
2011            }
2012            i += 1;
2013        }
2014
2015        // Skip newlines after `)`
2016        while i < self.tokens.len() && self.tokens[i].kind == TokenKind::Newline {
2017            i += 1;
2018        }
2019
2020        matches!(
2021            self.tokens.get(i).map(|t| &t.kind),
2022            Some(TokenKind::FatArrow)
2023        )
2024    }
2025
2026    /// Parse `(params) => body`.
2027    fn parse_lambda(&mut self) -> Expr {
2028        let start = self.peek().span;
2029        let _ = self.advance(); // consume `(`
2030        let params = self.parse_lambda_param_list();
2031        let _ = self.expect(TokenKind::RParen);
2032        let _ = self.expect(TokenKind::FatArrow);
2033
2034        // Body: block or single expression
2035        let body = if self.at(TokenKind::LBrace) {
2036            let block = self.parse_block();
2037            let bspan = block.span;
2038            Expr::Block {
2039                id: self.alloc_id(),
2040                span: bspan,
2041                block,
2042            }
2043        } else {
2044            self.parse_expr()
2045        };
2046
2047        let id = self.alloc_id();
2048        let span = Span::merge(start, body.span());
2049        Expr::Lambda {
2050            id,
2051            span,
2052            params,
2053            body: Box::new(body),
2054        }
2055    }
2056
2057    /// Parse lambda params (simplified: `ident [: type]` separated by commas).
2058    fn parse_lambda_param_list(&mut self) -> Vec<Param> {
2059        let mut params = Vec::new();
2060        self.skip_newlines();
2061
2062        while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
2063            let id = self.alloc_id();
2064            let start = self.peek().span;
2065
2066            let pattern = match self.peek().kind.clone() {
2067                TokenKind::Ident => {
2068                    let tok = self.advance();
2069                    let span = tok.span;
2070                    Pattern::Bind {
2071                        id: self.alloc_id(),
2072                        span,
2073                        name: Ident {
2074                            name: tok.literal.unwrap_or_default(),
2075                            span,
2076                        },
2077                    }
2078                }
2079                TokenKind::Underscore => {
2080                    let tok = self.advance();
2081                    Pattern::Wildcard {
2082                        id: self.alloc_id(),
2083                        span: tok.span,
2084                    }
2085                }
2086                TokenKind::Mut => {
2087                    let _ = self.advance(); // consume `mut`
2088                    let tok = if self.at(TokenKind::Ident) {
2089                        self.advance()
2090                    } else {
2091                        return params; // error recovery
2092                    };
2093                    let span = tok.span;
2094                    Pattern::MutBind {
2095                        id: self.alloc_id(),
2096                        span,
2097                        name: Ident {
2098                            name: tok.literal.unwrap_or_default(),
2099                            span,
2100                        },
2101                    }
2102                }
2103                _ => break,
2104            };
2105
2106            let ty = if self.at(TokenKind::Colon) {
2107                let _ = self.advance();
2108                Some(self.parse_type_expr())
2109            } else {
2110                None
2111            };
2112
2113            let end = self.peek().span;
2114            params.push(Param {
2115                id,
2116                span: Span::merge(start, end),
2117                pattern,
2118                ty,
2119                default: None,
2120            });
2121
2122            self.skip_newlines();
2123            if self.at(TokenKind::Comma) {
2124                let _ = self.advance();
2125                self.skip_newlines();
2126            } else {
2127                break;
2128            }
2129        }
2130
2131        params
2132    }
2133
2134    /// Parse `(expr)` grouping or `(a, b, ...)` tuple.
2135    fn parse_paren_or_tuple(&mut self) -> Expr {
2136        let start = self.peek().span;
2137        let _ = self.advance(); // consume `(`
2138
2139        self.skip_newlines();
2140
2141        // Empty parens → unit
2142        if self.at(TokenKind::RParen) {
2143            let end = self.advance().span;
2144            let id = self.alloc_id();
2145            return Expr::Literal {
2146                id,
2147                span: Span::merge(start, end),
2148                lit: Literal::Unit,
2149            };
2150        }
2151
2152        let first = self.parse_expr();
2153        self.skip_newlines();
2154
2155        if self.at(TokenKind::Comma) {
2156            // Tuple
2157            let mut elems = vec![first];
2158            while self.at(TokenKind::Comma) {
2159                let _ = self.advance();
2160                self.skip_newlines();
2161                if self.at(TokenKind::RParen) {
2162                    break;
2163                }
2164                elems.push(self.parse_expr());
2165                self.skip_newlines();
2166            }
2167            let end = self
2168                .expect(TokenKind::RParen)
2169                .map(|t| t.span)
2170                .unwrap_or(start);
2171            let id = self.alloc_id();
2172            Expr::TupleLiteral {
2173                id,
2174                span: Span::merge(start, end),
2175                elems,
2176            }
2177        } else {
2178            // Grouped expression
2179            let end = self
2180                .expect(TokenKind::RParen)
2181                .map(|t| t.span)
2182                .unwrap_or(start);
2183            let mut e = first;
2184            // Update span to include parens
2185            if let Some(new_span) = Some(Span::merge(start, end)) {
2186                match &mut e {
2187                    Expr::Literal { span, .. }
2188                    | Expr::Identifier { span, .. }
2189                    | Expr::Binary { span, .. }
2190                    | Expr::Unary { span, .. } => *span = new_span,
2191                    _ => {}
2192                }
2193            }
2194            e
2195        }
2196    }
2197
2198    /// Parse a list literal `[elem, ...]`.
2199    fn parse_list_literal(&mut self) -> Expr {
2200        let start = self.peek().span;
2201        let _ = self.advance(); // consume `[`
2202        let mut elems = Vec::new();
2203        self.skip_newlines();
2204
2205        while !self.at(TokenKind::RBracket) && !self.at(TokenKind::Eof) {
2206            elems.push(self.parse_expr());
2207            self.skip_newlines();
2208            if self.at(TokenKind::Comma) {
2209                let _ = self.advance();
2210                self.skip_newlines();
2211            } else {
2212                break;
2213            }
2214        }
2215
2216        let end = self
2217            .expect(TokenKind::RBracket)
2218            .map(|t| t.span)
2219            .unwrap_or(start);
2220        let id = self.alloc_id();
2221        Expr::ListLiteral {
2222            id,
2223            span: Span::merge(start, end),
2224            elems,
2225        }
2226    }
2227
2228    /// Parse a set literal `#{elem, ...}`.
2229    fn parse_set_literal(&mut self) -> Expr {
2230        let start = self.peek().span;
2231        let _ = self.advance(); // consume `#`
2232        let _ = self.advance(); // consume `{`
2233        let mut elems = Vec::new();
2234        self.skip_newlines();
2235
2236        while !self.at(TokenKind::RBrace) && !self.at(TokenKind::Eof) {
2237            elems.push(self.parse_expr());
2238            self.skip_newlines();
2239            if self.at(TokenKind::Comma) {
2240                let _ = self.advance();
2241                self.skip_newlines();
2242            } else {
2243                break;
2244            }
2245        }
2246
2247        let end = self
2248            .expect(TokenKind::RBrace)
2249            .map(|t| t.span)
2250            .unwrap_or(start);
2251        let id = self.alloc_id();
2252        Expr::SetLiteral {
2253            id,
2254            span: Span::merge(start, end),
2255            elems,
2256        }
2257    }
2258
2259    /// Check if the current position is an empty brace pair `{}`,
2260    /// allowing for intervening newlines.
2261    fn is_empty_brace(&self) -> bool {
2262        if self.peek().kind != TokenKind::LBrace {
2263            return false;
2264        }
2265        let mut i = self.pos + 1;
2266        while i < self.tokens.len() && self.tokens[i].kind == TokenKind::Newline {
2267            i += 1;
2268        }
2269        i < self.tokens.len() && self.tokens[i].kind == TokenKind::RBrace
2270    }
2271
2272    /// Check if a type annotation refers to `Map[...]`.
2273    fn is_map_type_annotation(ty: &Option<TypeExpr>) -> bool {
2274        matches!(ty, Some(TypeExpr::Named { path, .. })
2275            if path.segments.last().map(|s| s.name.as_str()) == Some("Map"))
2276    }
2277
2278    /// Lookahead: does `{` start a map literal (first element is `expr ':'`)?
2279    fn is_map_literal_start(&self) -> bool {
2280        // We're at `{`. Look at the first non-newline token after it.
2281        let mut i = self.pos + 1;
2282        while i < self.tokens.len() && self.tokens[i].kind == TokenKind::Newline {
2283            i += 1;
2284        }
2285        if i >= self.tokens.len() {
2286            return false;
2287        }
2288        // If the first token is a string/int/float literal, and next is `:`, it's a map.
2289        let is_map_key_start = matches!(
2290            &self.tokens[i].kind,
2291            TokenKind::StringLiteral
2292                | TokenKind::RawStringLiteral
2293                | TokenKind::RawMultiLineStringLiteral
2294                | TokenKind::IntLiteral
2295                | TokenKind::FloatLiteral
2296                | TokenKind::Ident
2297                | TokenKind::TypeIdent
2298        );
2299        if !is_map_key_start || i + 1 >= self.tokens.len() {
2300            return false;
2301        }
2302        self.tokens[i + 1].kind == TokenKind::Colon
2303    }
2304
2305    /// Parse a map literal `{ key: val, ... }`.
2306    fn parse_map_literal(&mut self) -> Expr {
2307        let start = self.peek().span;
2308        let _ = self.advance(); // consume `{`
2309        let mut entries = Vec::new();
2310        self.skip_newlines();
2311
2312        while !self.at(TokenKind::RBrace) && !self.at(TokenKind::Eof) {
2313            let key = self.parse_expr();
2314            let _ = self.expect(TokenKind::Colon);
2315            let val = self.parse_expr();
2316            entries.push((key, val));
2317            self.skip_newlines();
2318            if self.at(TokenKind::Comma) {
2319                let _ = self.advance();
2320                self.skip_newlines();
2321            } else {
2322                break;
2323            }
2324        }
2325
2326        let end = self
2327            .expect(TokenKind::RBrace)
2328            .map(|t| t.span)
2329            .unwrap_or(start);
2330        let id = self.alloc_id();
2331        Expr::MapLiteral {
2332            id,
2333            span: Span::merge(start, end),
2334            entries,
2335        }
2336    }
2337
2338    /// Parse an `if` / `if-let` expression.
2339    fn parse_if_expr(&mut self) -> Expr {
2340        let start = self.peek().span;
2341        let _ = self.advance(); // consume `if`
2342
2343        // The condition must be parenthesized: `if (cond) { ... }`. A missing
2344        // `(` here used to fall into the generic E2000 "expected `(`" catch-all
2345        // (via `expect`), which states the token but not the rule or the fix.
2346        // Emit the purpose-built parens-required diagnostic (E2030) with a
2347        // concrete one-edit repair so an agent can fix it from the text alone
2348        // (Q-diag-structure-misc (b)). Then keep parsing as if the `(` were
2349        // present so a real condition is still read.
2350        let had_lparen = if self.at(TokenKind::LParen) {
2351            let _ = self.advance();
2352            true
2353        } else {
2354            let span = self.peek().span;
2355            let found = self.peek().kind.clone();
2356            self.diagnostics
2357                .error(
2358                    DiagnosticCode {
2359                        prefix: 'E',
2360                        number: 2030,
2361                    },
2362                    format!("`if` condition must be parenthesized, found `{found}`"),
2363                    span,
2364                )
2365                .note("wrap the condition in parentheses: `if (cond) { ... }`");
2366            false
2367        };
2368
2369        // Check for `if-let`: `if (let Some(v) = expr)`
2370        let (let_pattern, condition) = if self.at(TokenKind::Let) {
2371            let _ = self.advance(); // consume `let`
2372            let pat = self.parse_pattern();
2373            let _ = self.expect(TokenKind::Assign);
2374            let cond = self.parse_expr();
2375            (Some(pat), cond)
2376        } else {
2377            (None, self.parse_expr())
2378        };
2379
2380        // Only require the closing `)` if we saw the opening one; otherwise the
2381        // single E2030 already reported the missing parens and a second
2382        // "expected `)`" would be noise for the same root cause.
2383        if had_lparen {
2384            let _ = self.expect(TokenKind::RParen);
2385        }
2386        self.skip_newlines();
2387
2388        let then_block = self.parse_block();
2389
2390        // Continuation rule (spec §3.2 rule 8): next line starts with `else`
2391        // → allow `else` on a new line after closing brace.
2392        if self.at(TokenKind::Newline) && self.peek_past_newlines_kind() == Some(TokenKind::Else) {
2393            self.skip_newlines();
2394        }
2395
2396        // Optional `else` branch
2397        let else_block = if self.at(TokenKind::Else) {
2398            let _ = self.advance(); // consume `else`
2399            self.skip_newlines();
2400            if self.at(TokenKind::If) {
2401                // `else if` chain
2402                Some(Box::new(self.parse_if_expr()))
2403            } else {
2404                let block = self.parse_block();
2405                let bspan = block.span;
2406                Some(Box::new(Expr::Block {
2407                    id: self.alloc_id(),
2408                    span: bspan,
2409                    block,
2410                }))
2411            }
2412        } else {
2413            None
2414        };
2415
2416        let end = else_block
2417            .as_ref()
2418            .map(|e| e.span())
2419            .unwrap_or(then_block.span);
2420        let id = self.alloc_id();
2421        Expr::If {
2422            id,
2423            span: Span::merge(start, end),
2424            let_pattern,
2425            condition: Box::new(condition),
2426            then_block,
2427            else_block,
2428        }
2429    }
2430
2431    /// Parse a `match` expression.
2432    fn parse_match_expr(&mut self) -> Expr {
2433        let start = self.peek().span;
2434        let _ = self.advance(); // consume `match`
2435
2436        let scrutinee = self.parse_expr();
2437        self.skip_newlines();
2438
2439        let _ = self.expect(TokenKind::LBrace);
2440        let mut arms = Vec::new();
2441
2442        loop {
2443            self.skip_newlines();
2444            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
2445                break;
2446            }
2447            // Parse pattern
2448            let arm_start = self.peek().span;
2449            let pattern = self.parse_pattern();
2450
2451            // Optional guard `if (condition)`
2452            let guard = if self.at(TokenKind::If) {
2453                let _ = self.advance(); // consume `if`
2454                let _ = self.expect(TokenKind::LParen);
2455                let g = self.parse_expr();
2456                let _ = self.expect(TokenKind::RParen);
2457                Some(g)
2458            } else {
2459                None
2460            };
2461
2462            let _ = self.expect(TokenKind::FatArrow);
2463            self.skip_newlines();
2464
2465            // Body: block or expression
2466            let body = if self.at(TokenKind::LBrace) {
2467                let block = self.parse_block();
2468                let bspan = block.span;
2469                Expr::Block {
2470                    id: self.alloc_id(),
2471                    span: bspan,
2472                    block,
2473                }
2474            } else {
2475                self.parse_expr()
2476            };
2477
2478            let arm_end = body.span();
2479            arms.push(MatchArm {
2480                id: self.alloc_id(),
2481                span: Span::merge(arm_start, arm_end),
2482                pattern,
2483                guard,
2484                body,
2485            });
2486
2487            self.skip_newlines();
2488            // Optional comma after arm
2489            if self.at(TokenKind::Comma) {
2490                let _ = self.advance();
2491            }
2492        }
2493
2494        let end = self
2495            .expect(TokenKind::RBrace)
2496            .map(|t| t.span)
2497            .unwrap_or(start);
2498        let id = self.alloc_id();
2499        Expr::Match {
2500            id,
2501            span: Span::merge(start, end),
2502            scrutinee: Box::new(scrutinee),
2503            arms,
2504        }
2505    }
2506
2507    /// Parse a pattern (for `match`, `let`, etc.).
2508    pub(crate) fn parse_pattern(&mut self) -> Pattern {
2509        // First parse a simple pattern, then check for `|` (or-pattern)
2510        let first = self.parse_simple_pattern();
2511
2512        if self.at(TokenKind::BitOr) {
2513            let start = first.span();
2514            let mut alternatives = vec![first];
2515            while self.at(TokenKind::BitOr) {
2516                let _ = self.advance(); // consume `|`
2517                self.skip_newlines();
2518                alternatives.push(self.parse_simple_pattern());
2519            }
2520            let end = alternatives.last().map(|p| p.span()).unwrap_or(start);
2521            Pattern::Or {
2522                id: self.alloc_id(),
2523                span: Span::merge(start, end),
2524                alternatives,
2525            }
2526        } else {
2527            first
2528        }
2529    }
2530
2531    /// Parse a single pattern (no or-pattern).
2532    fn parse_simple_pattern(&mut self) -> Pattern {
2533        let id = self.alloc_id();
2534        let span = self.peek().span;
2535
2536        match self.peek().kind.clone() {
2537            // `_` wildcard
2538            TokenKind::Underscore => {
2539                let _ = self.advance();
2540                Pattern::Wildcard { id, span }
2541            }
2542            // `mut name`
2543            TokenKind::Mut => {
2544                let _ = self.advance(); // consume `mut`
2545                let tok = if self.at(TokenKind::Ident) {
2546                    self.advance()
2547                } else {
2548                    return Pattern::Wildcard { id, span };
2549                };
2550                let name = Ident {
2551                    name: tok.literal.unwrap_or_default(),
2552                    span: tok.span,
2553                };
2554                Pattern::MutBind {
2555                    id,
2556                    span: Span::merge(span, tok.span),
2557                    name,
2558                }
2559            }
2560            // `..` rest pattern
2561            TokenKind::DotDot => {
2562                let _ = self.advance();
2563                Pattern::Rest { id, span }
2564            }
2565            // Literal patterns
2566            TokenKind::IntLiteral => {
2567                let tok = self.advance();
2568                let lit = Literal::Int(tok.literal.unwrap_or_default());
2569                let pat = Pattern::Literal {
2570                    id,
2571                    span: tok.span,
2572                    lit,
2573                };
2574                // Check for range pattern: `1..10` or `1..=10`
2575                self.try_parse_range_pattern(pat)
2576            }
2577            TokenKind::Minus => {
2578                // Negative literal
2579                let _ = self.advance();
2580                if self.at(TokenKind::IntLiteral) {
2581                    let tok = self.advance();
2582                    let lit = Literal::Int(format!("-{}", tok.literal.unwrap_or_default()));
2583                    let pat = Pattern::Literal {
2584                        id,
2585                        span: Span::merge(span, tok.span),
2586                        lit,
2587                    };
2588                    self.try_parse_range_pattern(pat)
2589                } else if self.at(TokenKind::FloatLiteral) {
2590                    let tok = self.advance();
2591                    let lit = Literal::Float(format!("-{}", tok.literal.unwrap_or_default()));
2592                    Pattern::Literal {
2593                        id,
2594                        span: Span::merge(span, tok.span),
2595                        lit,
2596                    }
2597                } else {
2598                    Pattern::Wildcard { id, span }
2599                }
2600            }
2601            TokenKind::FloatLiteral => {
2602                let tok = self.advance();
2603                Pattern::Literal {
2604                    id,
2605                    span: tok.span,
2606                    lit: Literal::Float(tok.literal.unwrap_or_default()),
2607                }
2608            }
2609            TokenKind::StringLiteral
2610            | TokenKind::RawStringLiteral
2611            | TokenKind::MultiLineStringLiteral
2612            | TokenKind::RawMultiLineStringLiteral => {
2613                let tok = self.advance();
2614                Pattern::Literal {
2615                    id,
2616                    span: tok.span,
2617                    lit: Literal::String(tok.literal.unwrap_or_default()),
2618                }
2619            }
2620            TokenKind::BoolLiteral => {
2621                let tok = self.advance();
2622                let val = self.source.slice(tok.span) == "true";
2623                Pattern::Literal {
2624                    id,
2625                    span: tok.span,
2626                    lit: Literal::Bool(val),
2627                }
2628            }
2629            // `TypeIdent` — constructor or record pattern
2630            TokenKind::TypeIdent
2631            | TokenKind::Ok_
2632            | TokenKind::Err_
2633            | TokenKind::Some_
2634            | TokenKind::None_ => {
2635                let tok = self.advance();
2636                // For keyword tokens (Ok, Err, Some, None), `literal` is None,
2637                // so fall back to the token kind's display name.
2638                let name = tok.literal.unwrap_or_else(|| tok.kind.to_string());
2639                let path_name = Ident {
2640                    name,
2641                    span: tok.span,
2642                };
2643                let path = TypePath {
2644                    segments: vec![path_name],
2645                    span: tok.span,
2646                };
2647
2648                if self.at(TokenKind::LParen) {
2649                    // Constructor pattern: `Type(fields...)`
2650                    let _ = self.advance();
2651                    let mut fields = Vec::new();
2652                    self.skip_newlines();
2653                    while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
2654                        fields.push(self.parse_pattern());
2655                        self.skip_newlines();
2656                        if self.at(TokenKind::Comma) {
2657                            let _ = self.advance();
2658                            self.skip_newlines();
2659                        } else {
2660                            break;
2661                        }
2662                    }
2663                    let end = self
2664                        .expect(TokenKind::RParen)
2665                        .map(|t| t.span)
2666                        .unwrap_or(span);
2667                    Pattern::Constructor {
2668                        id,
2669                        span: Span::merge(span, end),
2670                        path,
2671                        fields,
2672                    }
2673                } else if self.at(TokenKind::LBrace) {
2674                    // Record pattern: `Type { field, field: pat }` or `Type { field: pat, .. }`
2675                    let _ = self.advance();
2676                    let mut fields = Vec::new();
2677                    let mut rest = false;
2678                    self.skip_newlines();
2679                    while !self.at(TokenKind::RBrace) && !self.at(TokenKind::Eof) {
2680                        if self.at(TokenKind::DotDot) {
2681                            let _ = self.advance(); // consume `..`
2682                            rest = true;
2683                            self.skip_newlines();
2684                            break;
2685                        } else if self.at(TokenKind::Ident) {
2686                            let ftok = self.advance();
2687                            let fname = Ident {
2688                                name: ftok.literal.unwrap_or_default(),
2689                                span: ftok.span,
2690                            };
2691                            let fpat = if self.at(TokenKind::Colon) {
2692                                let _ = self.advance();
2693                                Some(self.parse_pattern())
2694                            } else {
2695                                None // shorthand
2696                            };
2697                            fields.push(RecordPatternField {
2698                                span: fname.span,
2699                                name: fname,
2700                                pattern: fpat,
2701                            });
2702                        } else {
2703                            break;
2704                        }
2705                        self.skip_newlines();
2706                        if self.at(TokenKind::Comma) {
2707                            let _ = self.advance();
2708                            self.skip_newlines();
2709                        } else {
2710                            break;
2711                        }
2712                    }
2713                    let end = self
2714                        .expect(TokenKind::RBrace)
2715                        .map(|t| t.span)
2716                        .unwrap_or(span);
2717                    Pattern::Record {
2718                        id,
2719                        span: Span::merge(span, end),
2720                        path,
2721                        fields,
2722                        rest,
2723                    }
2724                } else {
2725                    // Unit constructor
2726                    Pattern::Constructor {
2727                        id,
2728                        span: path.span,
2729                        path,
2730                        fields: vec![],
2731                    }
2732                }
2733            }
2734            // Lowercase ident — bind pattern
2735            TokenKind::Ident => {
2736                let tok = self.advance();
2737                let name = Ident {
2738                    name: tok.literal.unwrap_or_default(),
2739                    span: tok.span,
2740                };
2741                Pattern::Bind {
2742                    id,
2743                    span: tok.span,
2744                    name,
2745                }
2746            }
2747            // `(a, b)` tuple pattern
2748            TokenKind::LParen => {
2749                let _ = self.advance();
2750                let mut elems = Vec::new();
2751                self.skip_newlines();
2752                while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
2753                    elems.push(self.parse_pattern());
2754                    self.skip_newlines();
2755                    if self.at(TokenKind::Comma) {
2756                        let _ = self.advance();
2757                        self.skip_newlines();
2758                    } else {
2759                        break;
2760                    }
2761                }
2762                let end = self
2763                    .expect(TokenKind::RParen)
2764                    .map(|t| t.span)
2765                    .unwrap_or(span);
2766                if elems.len() == 1 {
2767                    elems.remove(0) // unwrap single-element parens
2768                } else {
2769                    Pattern::Tuple {
2770                        id,
2771                        span: Span::merge(span, end),
2772                        elems,
2773                    }
2774                }
2775            }
2776            // `[head, ..tail]` list pattern
2777            TokenKind::LBracket => {
2778                let _ = self.advance();
2779                let mut elems = Vec::new();
2780                let mut rest = None;
2781                self.skip_newlines();
2782
2783                while !self.at(TokenKind::RBracket) && !self.at(TokenKind::Eof) {
2784                    if self.at(TokenKind::DotDot) {
2785                        let rest_start = self.peek().span;
2786                        let _ = self.advance(); // consume `..`
2787                        if self.at(TokenKind::Ident) {
2788                            let tok = self.advance();
2789                            let name = Ident {
2790                                name: tok.literal.unwrap_or_default(),
2791                                span: tok.span,
2792                            };
2793                            rest = Some(Box::new(Pattern::Bind {
2794                                id: self.alloc_id(),
2795                                span: Span::merge(rest_start, tok.span),
2796                                name,
2797                            }));
2798                        } else {
2799                            rest = Some(Box::new(Pattern::Rest {
2800                                id: self.alloc_id(),
2801                                span: rest_start,
2802                            }));
2803                        }
2804                        self.skip_newlines();
2805                        break;
2806                    }
2807                    elems.push(self.parse_pattern());
2808                    self.skip_newlines();
2809                    if self.at(TokenKind::Comma) {
2810                        let _ = self.advance();
2811                        self.skip_newlines();
2812                    } else {
2813                        break;
2814                    }
2815                }
2816
2817                let end = self
2818                    .expect(TokenKind::RBracket)
2819                    .map(|t| t.span)
2820                    .unwrap_or(span);
2821                Pattern::List {
2822                    id,
2823                    span: Span::merge(span, end),
2824                    elems,
2825                    rest,
2826                }
2827            }
2828            _ => {
2829                self.diagnostics.error(
2830                    DiagnosticCode {
2831                        prefix: 'E',
2832                        number: 2021,
2833                    },
2834                    format!("expected pattern, found `{}`", self.peek().kind),
2835                    span,
2836                );
2837                if !self.at(TokenKind::Eof) {
2838                    let _ = self.advance();
2839                }
2840                Pattern::Wildcard { id, span }
2841            }
2842        }
2843    }
2844
2845    /// Check if the pattern is followed by `..` or `..=` to form a range pattern.
2846    fn try_parse_range_pattern(&mut self, lo: Pattern) -> Pattern {
2847        if self.at(TokenKind::DotDot) || self.at(TokenKind::DotDotEq) {
2848            let inclusive = self.at(TokenKind::DotDotEq);
2849            let _ = self.advance();
2850            let hi = self.parse_simple_pattern();
2851            let span = Span::merge(lo.span(), hi.span());
2852            Pattern::Range {
2853                id: self.alloc_id(),
2854                span,
2855                lo: Box::new(lo),
2856                hi: Box::new(hi),
2857                inclusive,
2858            }
2859        } else {
2860            lo
2861        }
2862    }
2863
2864    /// Returns `true` if the current token terminates a statement (newline, `;`, `}`, or EOF).
2865    fn at_stmt_terminator(&self) -> bool {
2866        matches!(
2867            self.peek().kind,
2868            TokenKind::Newline | TokenKind::Semicolon | TokenKind::RBrace | TokenKind::Eof
2869        )
2870    }
2871
2872    // ─── Block parsing ────────────────────────────────────────────────────────
2873
2874    /// Parse a block `{ stmts... [tail_expr] }`.
2875    fn parse_block(&mut self) -> Block {
2876        let start = self.peek().span;
2877        if self.expect(TokenKind::LBrace).is_err() {
2878            return Block {
2879                id: self.alloc_id(),
2880                span: start,
2881                stmts: vec![],
2882                tail: None,
2883            };
2884        }
2885
2886        let mut stmts = Vec::new();
2887        let mut tail: Option<Box<Expr>> = None;
2888
2889        loop {
2890            self.skip_newlines();
2891            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
2892                break;
2893            }
2894            // Skip semicolons
2895            if self.at(TokenKind::Semicolon) {
2896                let _ = self.advance();
2897                continue;
2898            }
2899
2900            let _stmt_start = self.peek().span;
2901
2902            // Statements that begin with keywords
2903            match self.peek().kind.clone() {
2904                TokenKind::Let => {
2905                    stmts.push(Stmt::Let(self.parse_let_stmt()));
2906                    self.skip_newlines();
2907                    continue;
2908                }
2909                TokenKind::For => {
2910                    stmts.push(Stmt::For(self.parse_for_loop()));
2911                    self.skip_newlines();
2912                    continue;
2913                }
2914                TokenKind::While => {
2915                    stmts.push(Stmt::While(self.parse_while_loop()));
2916                    self.skip_newlines();
2917                    continue;
2918                }
2919                TokenKind::Loop => {
2920                    let expr = self.parse_loop_expr();
2921                    self.skip_newlines();
2922                    if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
2923                        tail = Some(Box::new(expr));
2924                        break;
2925                    }
2926                    stmts.push(Stmt::Expr(expr));
2927                    continue;
2928                }
2929                TokenKind::Guard => {
2930                    stmts.push(Stmt::Guard(self.parse_guard_stmt()));
2931                    self.skip_newlines();
2932                    continue;
2933                }
2934                TokenKind::Handling => {
2935                    stmts.push(Stmt::Handling(self.parse_handling_block()));
2936                    self.skip_newlines();
2937                    continue;
2938                }
2939                _ => {}
2940            }
2941
2942            // Parse an expression
2943            let expr = self.parse_expr();
2944
2945            // Check if this expression is the tail (not followed by a terminator in the same line,
2946            // and we're about to hit `}`).
2947            self.skip_newlines();
2948
2949            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
2950                // This expression is the tail value of the block
2951                tail = Some(Box::new(expr));
2952                break;
2953            }
2954
2955            // Assignment might have been parsed as assignment expr; treat as stmt
2956            if self.at(TokenKind::Semicolon) {
2957                let _ = self.advance();
2958            }
2959
2960            stmts.push(Stmt::Expr(expr));
2961        }
2962
2963        let fallback_end = stmts
2964            .last()
2965            .map(|s| match s {
2966                Stmt::Expr(e) => e.span(),
2967                Stmt::Let(l) => l.span,
2968                _ => start,
2969            })
2970            .or_else(|| tail.as_ref().map(|t| t.span()))
2971            .unwrap_or(start);
2972        let end = self
2973            .expect(TokenKind::RBrace)
2974            .map(|t| t.span)
2975            .unwrap_or(fallback_end);
2976        Block {
2977            id: self.alloc_id(),
2978            span: Span::merge(start, end),
2979            stmts,
2980            tail,
2981        }
2982    }
2983
2984    /// Parse `let [mut] pattern [: Type] = expr`.
2985    fn parse_let_stmt(&mut self) -> LetStmt {
2986        let id = self.alloc_id();
2987        let start = self.peek().span;
2988        let _ = self.advance(); // consume `let`
2989
2990        let pattern = if self.at(TokenKind::Mut) {
2991            let mut_span = self.advance().span; // consume `mut`
2992            if self.at(TokenKind::Ident) {
2993                let tok = self.advance();
2994                let name = Ident {
2995                    name: tok.literal.unwrap_or_default(),
2996                    span: tok.span,
2997                };
2998                Pattern::MutBind {
2999                    id: self.alloc_id(),
3000                    span: Span::merge(mut_span, tok.span),
3001                    name,
3002                }
3003            } else {
3004                self.parse_pattern()
3005            }
3006        } else {
3007            self.parse_pattern()
3008        };
3009
3010        let ty = if self.at(TokenKind::Colon) {
3011            let _ = self.advance();
3012            Some(self.parse_type_expr())
3013        } else {
3014            None
3015        };
3016
3017        let _ = self.expect(TokenKind::Assign);
3018        let value = if self.is_empty_brace() && Self::is_map_type_annotation(&ty) {
3019            // FC-15: `let m: Map[K, V] = {}` → empty map literal, not empty block
3020            let open = self.advance().span; // consume `{`
3021            self.skip_newlines();
3022            let close_span = self
3023                .expect(TokenKind::RBrace)
3024                .map(|t| t.span)
3025                .unwrap_or(open);
3026            Expr::MapLiteral {
3027                id: self.alloc_id(),
3028                span: Span::merge(open, close_span),
3029                entries: vec![],
3030            }
3031        } else {
3032            self.parse_expr()
3033        };
3034        let end = value.span();
3035
3036        LetStmt {
3037            id,
3038            span: Span::merge(start, end),
3039            pattern,
3040            ty,
3041            value,
3042        }
3043    }
3044
3045    /// Parse `for pattern in expr { body }`.
3046    fn parse_for_loop(&mut self) -> ForLoop {
3047        let id = self.alloc_id();
3048        let start = self.peek().span;
3049        let _ = self.advance(); // consume `for`
3050
3051        let pattern = self.parse_pattern();
3052        let _ = self.expect(TokenKind::In);
3053        let iterable = self.parse_expr();
3054        self.skip_newlines();
3055        let body = self.parse_block();
3056
3057        let end = body.span;
3058        ForLoop {
3059            id,
3060            span: Span::merge(start, end),
3061            pattern,
3062            iterable,
3063            body,
3064        }
3065    }
3066
3067    /// Parse `while (condition) { body }`.
3068    fn parse_while_loop(&mut self) -> WhileLoop {
3069        let id = self.alloc_id();
3070        let start = self.peek().span;
3071        let _ = self.advance(); // consume `while`
3072
3073        let _ = self.expect(TokenKind::LParen);
3074        let condition = self.parse_expr();
3075        let _ = self.expect(TokenKind::RParen);
3076        self.skip_newlines();
3077        let body = self.parse_block();
3078
3079        let end = body.span;
3080        WhileLoop {
3081            id,
3082            span: Span::merge(start, end),
3083            condition,
3084            body,
3085        }
3086    }
3087
3088    /// Parse `loop { body }` as an expression.
3089    fn parse_loop_expr(&mut self) -> Expr {
3090        let id = self.alloc_id();
3091        let start = self.peek().span;
3092        let _ = self.advance(); // consume `loop`
3093        self.skip_newlines();
3094        let body = self.parse_block();
3095        let end = body.span;
3096        Expr::Loop {
3097            id,
3098            span: Span::merge(start, end),
3099            body,
3100        }
3101    }
3102
3103    /// Parse `guard (condition) else { diverging_block }`.
3104    fn parse_guard_stmt(&mut self) -> GuardStmt {
3105        let id = self.alloc_id();
3106        let start = self.peek().span;
3107        let _ = self.advance(); // consume `guard`
3108        let _ = self.expect(TokenKind::LParen);
3109
3110        // Check for `guard (let pat = expr)` — same condition production as `if`.
3111        let (let_pattern, condition) = if self.at(TokenKind::Let) {
3112            let _ = self.advance(); // consume `let`
3113            let pat = self.parse_pattern();
3114            let _ = self.expect(TokenKind::Assign);
3115            let cond = self.parse_expr();
3116            (Some(pat), cond)
3117        } else {
3118            (None, self.parse_expr())
3119        };
3120
3121        let _ = self.expect(TokenKind::RParen);
3122        let _ = self.expect(TokenKind::Else);
3123        self.skip_newlines();
3124        let else_block = self.parse_block();
3125        let end = else_block.span;
3126        GuardStmt {
3127            id,
3128            span: Span::merge(start, end),
3129            let_pattern,
3130            condition,
3131            else_block,
3132        }
3133    }
3134
3135    /// Parse `handling (Effect with handler, ...) { body }`.
3136    fn parse_handling_block(&mut self) -> HandlingBlock {
3137        let id = self.alloc_id();
3138        let start = self.peek().span;
3139        let _ = self.advance(); // consume `handling`
3140        let _ = self.expect(TokenKind::LParen);
3141
3142        let mut handlers = Vec::new();
3143        self.skip_newlines();
3144        while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
3145            let h_start = self.peek().span;
3146            let effect = self.parse_type_path();
3147            let _ = self.expect(TokenKind::With);
3148            let handler = self.parse_expr();
3149            let h_end = handler.span();
3150            handlers.push(HandlerPair {
3151                span: Span::merge(h_start, h_end),
3152                effect,
3153                handler,
3154            });
3155            self.skip_newlines();
3156            if self.at(TokenKind::Comma) {
3157                let _ = self.advance();
3158                self.skip_newlines();
3159            } else {
3160                break;
3161            }
3162        }
3163        let _ = self.expect(TokenKind::RParen);
3164        self.skip_newlines();
3165        let body = self.parse_block();
3166        let end = body.span;
3167        HandlingBlock {
3168            id,
3169            span: Span::merge(start, end),
3170            handlers,
3171            body,
3172        }
3173    }
3174
3175    // ─── Function declarations ────────────────────────────────────────────────
3176
3177    /// Parse a function declaration.
3178    ///
3179    /// ```text
3180    /// [vis] [async] fn IDENT [generic_params] ( [params] ) [-> type] [with effects] [where] block
3181    /// ```
3182    fn parse_fn_decl(&mut self, annotations: Vec<Annotation>, vis: Visibility) -> FnDecl {
3183        let start = self.peek().span;
3184
3185        // Optional `async` keyword.
3186        let is_async = if self.at(TokenKind::Async) {
3187            let _ = self.advance();
3188            true
3189        } else {
3190            false
3191        };
3192
3193        let _ = self.expect(TokenKind::Fn); // consume `fn`
3194
3195        // Function name — must be a lowercase identifier.
3196        let name_span = self.peek().span;
3197        let name = if self.at(TokenKind::Ident) {
3198            let tok = self.advance();
3199            Ident {
3200                name: tok.literal.unwrap_or_default(),
3201                span: tok.span,
3202            }
3203        } else {
3204            self.diagnostics.error(
3205                DiagnosticCode {
3206                    prefix: 'E',
3207                    number: 2073,
3208                },
3209                format!("expected function name, found `{}`", self.peek().kind),
3210                name_span,
3211            );
3212            Ident {
3213                name: String::new(),
3214                span: name_span,
3215            }
3216        };
3217
3218        // Generic params `[T, U: Bound]`.
3219        let generic_params = self.parse_generic_params();
3220
3221        // Parameter list `(x: Int, y: Int = 0)`.
3222        let _ = self.expect(TokenKind::LParen);
3223        let params = self.parse_param_list();
3224        let _ = self.expect(TokenKind::RParen);
3225
3226        // Return type `-> Type`.
3227        let return_type = if self.at(TokenKind::ThinArrow) {
3228            let _ = self.advance();
3229            Some(self.parse_type_expr())
3230        } else {
3231            None
3232        };
3233
3234        // Effect clause `with Effect1, Effect2` — may appear on the next line.
3235        if self.peek_past_newlines_kind() == Some(TokenKind::With) {
3236            self.skip_newlines();
3237        }
3238        let effect_clause = self.parse_effect_clause();
3239
3240        // Where clause `where (T: Bound)` — may appear on the next line.
3241        if self.peek_past_newlines_kind() == Some(TokenKind::Where) {
3242            self.skip_newlines();
3243        }
3244        let where_clause = self.parse_where_clause();
3245
3246        self.skip_newlines();
3247
3248        // Body block.
3249        let body = self.parse_block();
3250        let end = body.span;
3251
3252        FnDecl {
3253            id: self.alloc_id(),
3254            span: Span::merge(start, end),
3255            annotations,
3256            visibility: vis,
3257            is_async,
3258            name,
3259            generic_params,
3260            params,
3261            return_type,
3262            effect_clause,
3263            where_clause,
3264            body: Some(body),
3265        }
3266    }
3267
3268    /// Parse a comma-separated parameter list (inside the `(...)` already open).
3269    fn parse_param_list(&mut self) -> Vec<Param> {
3270        let mut params = Vec::new();
3271        self.skip_newlines();
3272
3273        while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
3274            params.push(self.parse_param());
3275            self.skip_newlines();
3276            if self.at(TokenKind::Comma) {
3277                let _ = self.advance();
3278                self.skip_newlines();
3279            } else {
3280                break;
3281            }
3282        }
3283
3284        params
3285    }
3286
3287    /// Parse a single function parameter: `['mut'] ( 'self' | IDENT ':' type_expr ) [= default]`.
3288    fn parse_param(&mut self) -> Param {
3289        let id = self.alloc_id();
3290        let start = self.peek().span;
3291
3292        // Pattern: optional `mut` prefix, then simple ident, `_`, or `self`.
3293        let pattern = match self.peek().kind.clone() {
3294            TokenKind::Mut => {
3295                let _ = self.advance(); // consume `mut`
3296                match self.peek().kind.clone() {
3297                    TokenKind::Ident => {
3298                        let tok = self.advance();
3299                        let span = tok.span;
3300                        Pattern::MutBind {
3301                            id: self.alloc_id(),
3302                            span,
3303                            name: Ident {
3304                                name: tok.literal.unwrap_or_default(),
3305                                span,
3306                            },
3307                        }
3308                    }
3309                    TokenKind::SelfLower => {
3310                        let tok = self.advance();
3311                        Pattern::MutBind {
3312                            id: self.alloc_id(),
3313                            span: tok.span,
3314                            name: Ident {
3315                                name: "self".into(),
3316                                span: tok.span,
3317                            },
3318                        }
3319                    }
3320                    _ => {
3321                        self.diagnostics.error(
3322                            DiagnosticCode {
3323                                prefix: 'E',
3324                                number: 2031,
3325                            },
3326                            format!(
3327                                "expected parameter name after `mut`, found `{}`",
3328                                self.peek().kind
3329                            ),
3330                            start,
3331                        );
3332                        Pattern::Wildcard {
3333                            id: self.alloc_id(),
3334                            span: start,
3335                        }
3336                    }
3337                }
3338            }
3339            TokenKind::Ident => {
3340                let tok = self.advance();
3341                let span = tok.span;
3342                Pattern::Bind {
3343                    id: self.alloc_id(),
3344                    span,
3345                    name: Ident {
3346                        name: tok.literal.unwrap_or_default(),
3347                        span,
3348                    },
3349                }
3350            }
3351            TokenKind::SelfLower => {
3352                let tok = self.advance();
3353                Pattern::Bind {
3354                    id: self.alloc_id(),
3355                    span: tok.span,
3356                    name: Ident {
3357                        name: "self".into(),
3358                        span: tok.span,
3359                    },
3360                }
3361            }
3362            TokenKind::Underscore => {
3363                let tok = self.advance();
3364                Pattern::Wildcard {
3365                    id: self.alloc_id(),
3366                    span: tok.span,
3367                }
3368            }
3369            _ => {
3370                self.diagnostics.error(
3371                    DiagnosticCode {
3372                        prefix: 'E',
3373                        number: 2031,
3374                    },
3375                    format!("expected parameter name, found `{}`", self.peek().kind),
3376                    start,
3377                );
3378                Pattern::Wildcard {
3379                    id: self.alloc_id(),
3380                    span: start,
3381                }
3382            }
3383        };
3384
3385        // Optional type annotation `: Type`.
3386        let ty = if self.at(TokenKind::Colon) {
3387            let _ = self.advance();
3388            Some(self.parse_type_expr())
3389        } else {
3390            None
3391        };
3392
3393        // Optional default value `= expr`.
3394        let default = if self.at(TokenKind::Assign) {
3395            let _ = self.advance();
3396            Some(self.parse_expr_stub())
3397        } else {
3398            None
3399        };
3400
3401        let end = self.peek().span;
3402        Param {
3403            id,
3404            span: Span::merge(start, end),
3405            pattern,
3406            ty,
3407            default,
3408        }
3409    }
3410
3411    /// Parse `with Effect1, Effect2, ...`.
3412    fn parse_effect_clause(&mut self) -> Vec<TypePath> {
3413        if !self.at(TokenKind::With) {
3414            return Vec::new();
3415        }
3416        let _ = self.advance(); // consume `with`
3417
3418        let mut effects = Vec::new();
3419        effects.push(self.parse_type_path());
3420
3421        while self.at(TokenKind::Comma) {
3422            let _ = self.advance();
3423            self.skip_newlines();
3424            effects.push(self.parse_type_path());
3425        }
3426
3427        effects
3428    }
3429
3430    // ─── Type alias declarations ─────────────────────────────────────────────
3431
3432    /// Parse a `type Name[T] = Type where (predicate)` alias declaration.
3433    fn parse_type_alias_decl(
3434        &mut self,
3435        annotations: Vec<Annotation>,
3436        vis: Visibility,
3437    ) -> TypeAliasDecl {
3438        let start = self.peek().span;
3439        let _ = self.advance(); // consume `type`
3440
3441        // Name — must be a type identifier (uppercase).
3442        let name_span = self.peek().span;
3443        let name = if self.at(TokenKind::TypeIdent) {
3444            let tok = self.advance();
3445            Ident {
3446                name: tok.literal.unwrap_or_default(),
3447                span: tok.span,
3448            }
3449        } else if self.at(TokenKind::Ident) {
3450            // Accept lowercase identifiers with a warning-free path for now.
3451            let tok = self.advance();
3452            Ident {
3453                name: tok.literal.unwrap_or_default(),
3454                span: tok.span,
3455            }
3456        } else {
3457            self.diagnostics.error(
3458                DiagnosticCode {
3459                    prefix: 'E',
3460                    number: 2060,
3461                },
3462                format!("expected type alias name, found `{}`", self.peek().kind),
3463                name_span,
3464            );
3465            Ident {
3466                name: String::new(),
3467                span: name_span,
3468            }
3469        };
3470
3471        // Optional generic params `[T, U]`.
3472        let generic_params = self.parse_generic_params();
3473
3474        // `=`
3475        let _ = self.expect(TokenKind::Assign);
3476
3477        // Type expression.
3478        let ty = self.parse_type_expr();
3479
3480        // Optional where clause `where (predicate)`.
3481        let where_clause = self.parse_where_clause();
3482
3483        let end = if !where_clause.is_empty() {
3484            where_clause
3485                .last()
3486                .expect("where_clause confirmed non-empty")
3487                .span
3488        } else {
3489            ty.span()
3490        };
3491
3492        TypeAliasDecl {
3493            id: self.alloc_id(),
3494            span: Span::merge(start, end),
3495            annotations,
3496            visibility: vis,
3497            name,
3498            generic_params,
3499            ty,
3500            where_clause,
3501        }
3502    }
3503
3504    // ─── Const declarations ──────────────────────────────────────────────────
3505
3506    /// Parse a `const NAME: Type = value` declaration.
3507    fn parse_const_decl(&mut self, annotations: Vec<Annotation>, vis: Visibility) -> ConstDecl {
3508        let start = self.peek().span;
3509        let _ = self.advance(); // consume `const`
3510
3511        // Name — accept both Ident and TypeIdent (constants are UPPER_CASE by convention).
3512        let name_span = self.peek().span;
3513        let name = if matches!(self.peek().kind, TokenKind::Ident | TokenKind::TypeIdent) {
3514            let tok = self.advance();
3515            Ident {
3516                name: tok.literal.unwrap_or_default(),
3517                span: tok.span,
3518            }
3519        } else {
3520            self.diagnostics.error(
3521                DiagnosticCode {
3522                    prefix: 'E',
3523                    number: 2061,
3524                },
3525                format!("expected constant name, found `{}`", self.peek().kind),
3526                name_span,
3527            );
3528            Ident {
3529                name: String::new(),
3530                span: name_span,
3531            }
3532        };
3533
3534        // `:`
3535        let _ = self.expect(TokenKind::Colon);
3536
3537        // Type expression.
3538        let ty = self.parse_type_expr();
3539
3540        // `=`
3541        let _ = self.expect(TokenKind::Assign);
3542
3543        // Value expression.
3544        let value = self.parse_expr();
3545
3546        let end = value.span();
3547
3548        ConstDecl {
3549            id: self.alloc_id(),
3550            span: Span::merge(start, end),
3551            annotations,
3552            visibility: vis,
3553            name,
3554            ty,
3555            value,
3556        }
3557    }
3558
3559    // ─── Record declarations ──────────────────────────────────────────────────
3560
3561    /// Parse a record (value-type) declaration.
3562    ///
3563    /// ```text
3564    /// [vis] record TypeIdent [generic_params] [where] { fields }
3565    /// ```
3566    fn parse_record_decl(&mut self, annotations: Vec<Annotation>, vis: Visibility) -> RecordDecl {
3567        let start = self.peek().span;
3568        let _ = self.advance(); // consume `record`
3569
3570        let name_span = self.peek().span;
3571        let name = if self.at(TokenKind::TypeIdent) {
3572            let tok = self.advance();
3573            Ident {
3574                name: tok.literal.unwrap_or_default(),
3575                span: tok.span,
3576            }
3577        } else {
3578            self.diagnostics.error(
3579                DiagnosticCode {
3580                    prefix: 'E',
3581                    number: 2040,
3582                },
3583                format!("expected record name, found `{}`", self.peek().kind),
3584                name_span,
3585            );
3586            Ident {
3587                name: String::new(),
3588                span: name_span,
3589            }
3590        };
3591
3592        let generic_params = self.parse_generic_params();
3593        let where_clause = self.parse_where_clause();
3594
3595        self.skip_newlines();
3596        let _ = self.expect(TokenKind::LBrace);
3597        let fields = self.parse_record_fields();
3598        let end = self
3599            .expect(TokenKind::RBrace)
3600            .map(|t| t.span)
3601            .unwrap_or(start);
3602
3603        RecordDecl {
3604            id: self.alloc_id(),
3605            span: Span::merge(start, end),
3606            annotations,
3607            visibility: vis,
3608            name,
3609            generic_params,
3610            where_clause,
3611            fields,
3612        }
3613    }
3614
3615    /// Parse fields inside a record body `{ name: Type [= default], ... }`.
3616    fn parse_record_fields(&mut self) -> Vec<RecordDeclField> {
3617        let mut fields = Vec::new();
3618
3619        loop {
3620            self.skip_newlines();
3621            // Skip doc comments attached to the next field; they're consumed
3622            // for doc-generation tooling but not stored on the AST.
3623            while self.at(TokenKind::DocComment) {
3624                let _ = self.advance();
3625                self.skip_newlines();
3626            }
3627            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
3628                break;
3629            }
3630            if !self.at(TokenKind::Ident) {
3631                break;
3632            }
3633
3634            let id = self.alloc_id();
3635            let start = self.peek().span;
3636            let tok = self.advance();
3637            let name = Ident {
3638                name: tok.literal.unwrap_or_default(),
3639                span: tok.span,
3640            };
3641
3642            let _ = self.expect(TokenKind::Colon);
3643            let ty = self.parse_type_expr();
3644
3645            let default = if self.at(TokenKind::Assign) {
3646                let _ = self.advance();
3647                Some(self.parse_expr_stub())
3648            } else {
3649                None
3650            };
3651
3652            let end = self.peek().span;
3653            fields.push(RecordDeclField {
3654                id,
3655                span: Span::merge(start, end),
3656                name,
3657                ty,
3658                default,
3659            });
3660
3661            self.skip_newlines();
3662            if self.at(TokenKind::Comma) {
3663                let _ = self.advance();
3664            }
3665        }
3666
3667        fields
3668    }
3669
3670    // ─── Enum declarations ────────────────────────────────────────────────────
3671
3672    /// Parse an enum (ADT) declaration.
3673    ///
3674    /// ```text
3675    /// [vis] enum TypeIdent [generic_params] [where] { variants }
3676    /// ```
3677    fn parse_enum_decl(&mut self, annotations: Vec<Annotation>, vis: Visibility) -> EnumDecl {
3678        let start = self.peek().span;
3679        let _ = self.advance(); // consume `enum`
3680
3681        let name_span = self.peek().span;
3682        let name = if self.at(TokenKind::TypeIdent) {
3683            let tok = self.advance();
3684            Ident {
3685                name: tok.literal.unwrap_or_default(),
3686                span: tok.span,
3687            }
3688        } else {
3689            self.diagnostics.error(
3690                DiagnosticCode {
3691                    prefix: 'E',
3692                    number: 2050,
3693                },
3694                format!("expected enum name, found `{}`", self.peek().kind),
3695                name_span,
3696            );
3697            Ident {
3698                name: String::new(),
3699                span: name_span,
3700            }
3701        };
3702
3703        let generic_params = self.parse_generic_params();
3704        let where_clause = self.parse_where_clause();
3705
3706        self.skip_newlines();
3707        let _ = self.expect(TokenKind::LBrace);
3708        let variants = self.parse_enum_variants();
3709        let end = self
3710            .expect(TokenKind::RBrace)
3711            .map(|t| t.span)
3712            .unwrap_or(start);
3713
3714        EnumDecl {
3715            id: self.alloc_id(),
3716            span: Span::merge(start, end),
3717            annotations,
3718            visibility: vis,
3719            name,
3720            generic_params,
3721            where_clause,
3722            variants,
3723        }
3724    }
3725
3726    /// Parse enum variants inside `{ ... }`.
3727    fn parse_enum_variants(&mut self) -> Vec<EnumVariant> {
3728        let mut variants = Vec::new();
3729
3730        loop {
3731            self.skip_newlines();
3732            while self.at(TokenKind::DocComment) {
3733                let _ = self.advance();
3734                self.skip_newlines();
3735            }
3736            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
3737                break;
3738            }
3739            // Accept TypeIdent and standard-library keyword variants (Ok, Err, Some, None).
3740            if !matches!(
3741                self.peek().kind,
3742                TokenKind::TypeIdent
3743                    | TokenKind::Ok_
3744                    | TokenKind::Err_
3745                    | TokenKind::Some_
3746                    | TokenKind::None_
3747            ) {
3748                break;
3749            }
3750
3751            let id = self.alloc_id();
3752            let start = self.peek().span;
3753            let tok = self.advance();
3754            // Use the display name for keyword variants (Ok_, Err_ → "Ok", "Err").
3755            let variant_name = tok.literal.unwrap_or_else(|| tok.kind.to_string());
3756            let name = Ident {
3757                name: variant_name,
3758                span: tok.span,
3759            };
3760
3761            let variant = if self.at(TokenKind::LBrace) {
3762                // Struct variant: `Name { field: Type, ... }`.
3763                let _ = self.advance();
3764                let fields = self.parse_record_fields();
3765                let end = self
3766                    .expect(TokenKind::RBrace)
3767                    .map(|t| t.span)
3768                    .unwrap_or(start);
3769                EnumVariant::Struct {
3770                    id,
3771                    span: Span::merge(start, end),
3772                    name,
3773                    fields,
3774                }
3775            } else if self.at(TokenKind::LParen) {
3776                // Tuple variant: `Name(Type, Type)`.
3777                let _ = self.advance();
3778                let mut tys = Vec::new();
3779                self.skip_newlines();
3780                while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
3781                    tys.push(self.parse_type_expr());
3782                    self.skip_newlines();
3783                    if self.at(TokenKind::Comma) {
3784                        let _ = self.advance();
3785                        self.skip_newlines();
3786                    } else {
3787                        break;
3788                    }
3789                }
3790                let end = self
3791                    .expect(TokenKind::RParen)
3792                    .map(|t| t.span)
3793                    .unwrap_or(start);
3794                EnumVariant::Tuple {
3795                    id,
3796                    span: Span::merge(start, end),
3797                    name,
3798                    tys,
3799                }
3800            } else {
3801                // Unit variant: just `Name`.
3802                EnumVariant::Unit {
3803                    id,
3804                    span: start,
3805                    name,
3806                }
3807            };
3808
3809            variants.push(variant);
3810
3811            self.skip_newlines();
3812            if self.at(TokenKind::Comma) {
3813                let _ = self.advance();
3814            }
3815        }
3816
3817        variants
3818    }
3819
3820    // ─── Class declarations ───────────────────────────────────────────────────
3821
3822    /// Parse a class declaration.
3823    ///
3824    /// ```text
3825    /// [vis] class TypeIdent [generic_params] [: Parent {, Trait}] [where] { fields methods }
3826    /// ```
3827    fn parse_class_decl(&mut self, annotations: Vec<Annotation>, vis: Visibility) -> ClassDecl {
3828        let start = self.peek().span;
3829        let _ = self.advance(); // consume `class`
3830
3831        let name_span = self.peek().span;
3832        let name = if self.at(TokenKind::TypeIdent) {
3833            let tok = self.advance();
3834            Ident {
3835                name: tok.literal.unwrap_or_default(),
3836                span: tok.span,
3837            }
3838        } else {
3839            self.diagnostics.error(
3840                DiagnosticCode {
3841                    prefix: 'E',
3842                    number: 2060,
3843                },
3844                format!("expected class name, found `{}`", self.peek().kind),
3845                name_span,
3846            );
3847            Ident {
3848                name: String::new(),
3849                span: name_span,
3850            }
3851        };
3852
3853        let generic_params = self.parse_generic_params();
3854
3855        // Optional `: Parent, Trait1, Trait2` inheritance/trait list.
3856        let mut base: Option<TypePath> = None;
3857        let mut traits: Vec<TypePath> = Vec::new();
3858
3859        if self.at(TokenKind::Colon) {
3860            let _ = self.advance(); // consume `:`
3861                                    // First entry may be a base class or a trait — we treat it as base.
3862            let first = self.parse_type_path();
3863            base = Some(first);
3864
3865            // Remaining comma-separated entries are traits.
3866            while self.at(TokenKind::Comma) {
3867                let _ = self.advance();
3868                self.skip_newlines();
3869                traits.push(self.parse_type_path());
3870            }
3871        }
3872
3873        let where_clause = self.parse_where_clause();
3874
3875        self.skip_newlines();
3876        let _ = self.expect(TokenKind::LBrace);
3877
3878        let (fields, methods) = self.parse_class_members();
3879
3880        let end = self
3881            .expect(TokenKind::RBrace)
3882            .map(|t| t.span)
3883            .unwrap_or(start);
3884
3885        ClassDecl {
3886            id: self.alloc_id(),
3887            span: Span::merge(start, end),
3888            annotations,
3889            visibility: vis,
3890            name,
3891            generic_params,
3892            base,
3893            traits,
3894            where_clause,
3895            fields,
3896            methods,
3897        }
3898    }
3899
3900    /// Parse the interior of a class body: a mix of field declarations and method declarations.
3901    fn parse_class_members(&mut self) -> (Vec<RecordDeclField>, Vec<FnDecl>) {
3902        let mut fields = Vec::new();
3903        let mut methods = Vec::new();
3904
3905        loop {
3906            self.skip_newlines();
3907            while self.at(TokenKind::DocComment) {
3908                let _ = self.advance();
3909                self.skip_newlines();
3910            }
3911            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
3912                break;
3913            }
3914
3915            // Leading annotations.
3916            let mut annotations = Vec::new();
3917            while self.at(TokenKind::At) {
3918                annotations.push(self.parse_annotation());
3919                self.skip_newlines();
3920            }
3921
3922            // Optional visibility.
3923            let vis = if self.at_visibility() {
3924                self.parse_visibility()
3925            } else {
3926                Visibility::Private
3927            };
3928
3929            match self.peek().kind.clone() {
3930                TokenKind::Fn | TokenKind::Async => {
3931                    methods.push(self.parse_fn_decl(annotations, vis));
3932                }
3933                TokenKind::Ident => {
3934                    // Field declaration: `name: Type [= default]`
3935                    let id = self.alloc_id();
3936                    let field_start = self.peek().span;
3937                    let tok = self.advance();
3938                    let field_name = Ident {
3939                        name: tok.literal.unwrap_or_default(),
3940                        span: tok.span,
3941                    };
3942                    let _ = self.expect(TokenKind::Colon);
3943                    let ty = self.parse_type_expr();
3944                    let default = if self.at(TokenKind::Assign) {
3945                        let _ = self.advance();
3946                        Some(self.parse_expr_stub())
3947                    } else {
3948                        None
3949                    };
3950                    let field_end = self.peek().span;
3951                    fields.push(RecordDeclField {
3952                        id,
3953                        span: Span::merge(field_start, field_end),
3954                        name: field_name,
3955                        ty,
3956                        default,
3957                    });
3958                    self.skip_newlines();
3959                    if self.at(TokenKind::Comma) {
3960                        let _ = self.advance();
3961                    }
3962                }
3963                _ => {
3964                    // Unrecognised — skip to avoid infinite loop.
3965                    let _ = self.advance();
3966                }
3967            }
3968        }
3969
3970        (fields, methods)
3971    }
3972
3973    // ─── Trait declarations ───────────────────────────────────────────────────
3974
3975    /// Parse a trait declaration (or platform trait when `is_platform` is true).
3976    ///
3977    /// ```text
3978    /// [vis] trait TypeIdent [generic_params] [where] { members }
3979    /// ```
3980    fn parse_trait_decl(
3981        &mut self,
3982        annotations: Vec<Annotation>,
3983        vis: Visibility,
3984        is_platform: bool,
3985    ) -> TraitDecl {
3986        let start = self.peek().span;
3987        let _ = self.expect(TokenKind::Trait); // consume `trait`
3988
3989        let name_span = self.peek().span;
3990        let name = if self.at(TokenKind::TypeIdent) {
3991            let tok = self.advance();
3992            Ident {
3993                name: tok.literal.unwrap_or_default(),
3994                span: tok.span,
3995            }
3996        } else {
3997            self.diagnostics.error(
3998                DiagnosticCode {
3999                    prefix: 'E',
4000                    number: 2070,
4001                },
4002                format!("expected trait name, found `{}`", self.peek().kind),
4003                name_span,
4004            );
4005            Ident {
4006                name: String::new(),
4007                span: name_span,
4008            }
4009        };
4010
4011        let generic_params = self.parse_generic_params();
4012
4013        // Optional supertrait list `: Supertrait1, Supertrait2`.
4014        let mut supertraits = Vec::new();
4015        if self.at(TokenKind::Colon) {
4016            let _ = self.advance();
4017            supertraits.push(self.parse_type_path());
4018            while self.at(TokenKind::Comma) {
4019                let _ = self.advance();
4020                self.skip_newlines();
4021                supertraits.push(self.parse_type_path());
4022            }
4023        }
4024
4025        let _where_clause = self.parse_where_clause();
4026
4027        self.skip_newlines();
4028        let _ = self.expect(TokenKind::LBrace);
4029
4030        let (associated_types, methods) = self.parse_trait_members();
4031
4032        let end = self
4033            .expect(TokenKind::RBrace)
4034            .map(|t| t.span)
4035            .unwrap_or(start);
4036
4037        TraitDecl {
4038            id: self.alloc_id(),
4039            span: Span::merge(start, end),
4040            annotations,
4041            visibility: vis,
4042            is_platform,
4043            name,
4044            generic_params,
4045            supertraits,
4046            associated_types,
4047            methods,
4048        }
4049    }
4050
4051    /// Parse the body of a `platform trait` — delegates to `parse_trait_decl` with the flag set.
4052    fn parse_platform_trait_decl(
4053        &mut self,
4054        annotations: Vec<Annotation>,
4055        vis: Visibility,
4056    ) -> TraitDecl {
4057        let _ = self.advance(); // consume `platform`
4058        self.parse_trait_decl(annotations, vis, true)
4059    }
4060
4061    /// Parse trait body members: associated type declarations and method declarations.
4062    ///
4063    /// Methods may be required (no body) or have a default implementation (with body).
4064    fn parse_trait_members(&mut self) -> (Vec<AssociatedType>, Vec<FnDecl>) {
4065        let mut assoc_types = Vec::new();
4066        let mut methods = Vec::new();
4067
4068        loop {
4069            self.skip_newlines();
4070            while self.at(TokenKind::DocComment) {
4071                let _ = self.advance();
4072                self.skip_newlines();
4073            }
4074            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
4075                break;
4076            }
4077
4078            // Leading annotations.
4079            let mut annotations = Vec::new();
4080            while self.at(TokenKind::At) {
4081                annotations.push(self.parse_annotation());
4082                self.skip_newlines();
4083            }
4084
4085            // Optional visibility.
4086            let vis = if self.at_visibility() {
4087                self.parse_visibility()
4088            } else {
4089                Visibility::Private
4090            };
4091
4092            match self.peek().kind.clone() {
4093                // `type AssocName [: Bound]`
4094                TokenKind::Type => {
4095                    let at_start = self.peek().span;
4096                    let _ = self.advance(); // consume `type`
4097                    let at_id = self.alloc_id();
4098                    let at_name_span = self.peek().span;
4099                    let at_name = if self.at(TokenKind::TypeIdent) {
4100                        let tok = self.advance();
4101                        Ident {
4102                            name: tok.literal.unwrap_or_default(),
4103                            span: tok.span,
4104                        }
4105                    } else {
4106                        self.diagnostics.error(
4107                            DiagnosticCode {
4108                                prefix: 'E',
4109                                number: 2071,
4110                            },
4111                            format!(
4112                                "expected associated type name, found `{}`",
4113                                self.peek().kind
4114                            ),
4115                            at_name_span,
4116                        );
4117                        Ident {
4118                            name: String::new(),
4119                            span: at_name_span,
4120                        }
4121                    };
4122
4123                    let bounds = if self.at(TokenKind::Colon) {
4124                        let _ = self.advance();
4125                        vec![self.parse_type_path()]
4126                    } else {
4127                        Vec::new()
4128                    };
4129
4130                    let at_end = self.peek().span;
4131                    assoc_types.push(AssociatedType {
4132                        id: at_id,
4133                        span: Span::merge(at_start, at_end),
4134                        name: at_name,
4135                        bounds,
4136                    });
4137                }
4138                TokenKind::Fn | TokenKind::Async => {
4139                    let fn_start = self.peek().span;
4140                    let is_async = if self.at(TokenKind::Async) {
4141                        let _ = self.advance();
4142                        true
4143                    } else {
4144                        false
4145                    };
4146                    let _ = self.expect(TokenKind::Fn);
4147
4148                    let fn_name_span = self.peek().span;
4149                    let fn_name = if self.at(TokenKind::Ident) {
4150                        let tok = self.advance();
4151                        Ident {
4152                            name: tok.literal.unwrap_or_default(),
4153                            span: tok.span,
4154                        }
4155                    } else {
4156                        self.diagnostics.error(
4157                            DiagnosticCode {
4158                                prefix: 'E',
4159                                number: 2072,
4160                            },
4161                            format!("expected method name, found `{}`", self.peek().kind),
4162                            fn_name_span,
4163                        );
4164                        Ident {
4165                            name: String::new(),
4166                            span: fn_name_span,
4167                        }
4168                    };
4169
4170                    let generic_params = self.parse_generic_params();
4171                    let _ = self.expect(TokenKind::LParen);
4172                    let params = self.parse_param_list();
4173                    let _ = self.expect(TokenKind::RParen);
4174
4175                    let return_type = if self.at(TokenKind::ThinArrow) {
4176                        let _ = self.advance();
4177                        Some(self.parse_type_expr())
4178                    } else {
4179                        None
4180                    };
4181
4182                    let effect_clause = self.parse_effect_clause();
4183                    let where_clause = self.parse_where_clause();
4184
4185                    self.skip_newlines();
4186
4187                    // Required methods have no body; default impls do.
4188                    let body = if self.at(TokenKind::LBrace) {
4189                        Some(self.parse_block())
4190                    } else {
4191                        None
4192                    };
4193
4194                    let fn_end = body
4195                        .as_ref()
4196                        .map(|b| b.span)
4197                        .unwrap_or_else(|| self.peek().span);
4198                    methods.push(FnDecl {
4199                        id: self.alloc_id(),
4200                        span: Span::merge(fn_start, fn_end),
4201                        annotations,
4202                        visibility: vis,
4203                        is_async,
4204                        name: fn_name,
4205                        generic_params,
4206                        params,
4207                        return_type,
4208                        effect_clause,
4209                        where_clause,
4210                        body,
4211                    });
4212                }
4213                _ => {
4214                    // Unrecognised — skip.
4215                    let _ = self.advance();
4216                }
4217            }
4218        }
4219
4220        (assoc_types, methods)
4221    }
4222
4223    // ─── Impl blocks ──────────────────────────────────────────────────────────
4224
4225    /// Parse an `impl` block.
4226    ///
4227    /// ```text
4228    /// impl [generic_params] [TraitPath for] TypeExpr [where] { methods }
4229    /// ```
4230    fn parse_impl_block(&mut self, annotations: Vec<Annotation>) -> ImplBlock {
4231        let start = self.peek().span;
4232        let _ = self.advance(); // consume `impl`
4233
4234        let generic_params = self.parse_generic_params();
4235
4236        // Determine whether this is `impl Trait for Type` or `impl Type`.
4237        // Disambiguate by scanning ahead: if we see `for` after a type path, it's a trait impl.
4238        let (trait_path, trait_args, target) = self.parse_impl_header();
4239
4240        let where_clause = self.parse_where_clause();
4241
4242        self.skip_newlines();
4243        let _ = self.expect(TokenKind::LBrace);
4244        let methods = self.parse_impl_methods();
4245        let end = self
4246            .expect(TokenKind::RBrace)
4247            .map(|t| t.span)
4248            .unwrap_or(start);
4249
4250        ImplBlock {
4251            id: self.alloc_id(),
4252            span: Span::merge(start, end),
4253            annotations,
4254            generic_params,
4255            trait_path,
4256            trait_args,
4257            target,
4258            where_clause,
4259            type_assignments: vec![],
4260            methods,
4261        }
4262    }
4263
4264    /// Parse the `[Trait for] Type` header of an impl block.
4265    ///
4266    /// Returns `(trait_path, trait_args, target)`. `trait_args` captures the
4267    /// type arguments applied to a parameterized trait, e.g. `[Int]` in
4268    /// `impl From[Int] for Float`; it is empty for `impl Type` and for
4269    /// non-parameterized traits.
4270    fn parse_impl_header(&mut self) -> (Option<TypePath>, Vec<TypeExpr>, TypeExpr) {
4271        // Parse the first type path and check if `for` follows.
4272        let first = self.parse_type_expr();
4273
4274        if self.at(TokenKind::For) {
4275            // `impl Trait for Type`
4276            let _ = self.advance(); // consume `for`
4277            let target = self.parse_type_expr();
4278            // Extract the type path and any type arguments from the first
4279            // TypeExpr (must be Named).
4280            let (trait_path, trait_args) = match &first {
4281                TypeExpr::Named { path, args, .. } => (Some(path.clone()), args.clone()),
4282                _ => (None, vec![]),
4283            };
4284            (trait_path, trait_args, target)
4285        } else {
4286            // `impl Type`
4287            (None, vec![], first)
4288        }
4289    }
4290
4291    /// Parse the methods inside an impl block body.
4292    fn parse_impl_methods(&mut self) -> Vec<FnDecl> {
4293        let mut methods = Vec::new();
4294
4295        loop {
4296            self.skip_newlines();
4297            while self.at(TokenKind::DocComment) {
4298                let _ = self.advance();
4299                self.skip_newlines();
4300            }
4301            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
4302                break;
4303            }
4304
4305            // Leading annotations.
4306            let mut annotations = Vec::new();
4307            while self.at(TokenKind::At) {
4308                annotations.push(self.parse_annotation());
4309                self.skip_newlines();
4310            }
4311
4312            // Optional visibility.
4313            let vis = if self.at_visibility() {
4314                self.parse_visibility()
4315            } else {
4316                Visibility::Private
4317            };
4318
4319            if matches!(self.peek().kind, TokenKind::Fn | TokenKind::Async) {
4320                methods.push(self.parse_fn_decl(annotations, vis));
4321            } else {
4322                // Unrecognised — skip.
4323                if self.at(TokenKind::Eof) {
4324                    break;
4325                }
4326                let _ = self.advance();
4327            }
4328        }
4329
4330        methods
4331    }
4332
4333    // ─── Effect declarations ──────────────────────────────────────────────────
4334
4335    /// Parse an effect declaration.
4336    ///
4337    /// ```text
4338    /// [vis] effect TypeIdent [generic_params] { fn_sig* }
4339    /// [vis] effect TypeIdent = TypePath + TypePath + ...   (composite)
4340    /// ```
4341    fn parse_effect_decl(&mut self, annotations: Vec<Annotation>, vis: Visibility) -> EffectDecl {
4342        let start = self.peek().span;
4343        let _ = self.advance(); // consume `effect`
4344
4345        let name_span = self.peek().span;
4346        let name = if self.at(TokenKind::TypeIdent) {
4347            let tok = self.advance();
4348            Ident {
4349                name: tok.literal.unwrap_or_default(),
4350                span: tok.span,
4351            }
4352        } else {
4353            self.diagnostics.error(
4354                DiagnosticCode {
4355                    prefix: 'E',
4356                    number: 2090,
4357                },
4358                format!("expected effect name, found `{}`", self.peek().kind),
4359                name_span,
4360            );
4361            Ident {
4362                name: String::new(),
4363                span: name_span,
4364            }
4365        };
4366
4367        let generic_params = self.parse_generic_params();
4368
4369        // Composite effect: `effect Name = TypePath + TypePath + ...`
4370        if self.at(TokenKind::Assign) {
4371            let _ = self.advance(); // consume `=`
4372            let mut components = vec![self.parse_type_path()];
4373            while self.at(TokenKind::Plus) {
4374                let _ = self.advance();
4375                self.skip_newlines();
4376                components.push(self.parse_type_path());
4377            }
4378            let end = self.peek().span;
4379            // Skip optional newline at end.
4380            if self.at(TokenKind::Newline) {
4381                let _ = self.advance();
4382            }
4383            return EffectDecl {
4384                id: self.alloc_id(),
4385                span: Span::merge(start, end),
4386                annotations,
4387                visibility: vis,
4388                name,
4389                generic_params,
4390                components,
4391                operations: Vec::new(),
4392            };
4393        }
4394
4395        // Regular effect with operation signatures.
4396        self.skip_newlines();
4397        let _ = self.expect(TokenKind::LBrace);
4398        let operations = self.parse_effect_operations();
4399        let end = self
4400            .expect(TokenKind::RBrace)
4401            .map(|t| t.span)
4402            .unwrap_or(start);
4403
4404        EffectDecl {
4405            id: self.alloc_id(),
4406            span: Span::merge(start, end),
4407            annotations,
4408            visibility: vis,
4409            name,
4410            generic_params,
4411            components: vec![],
4412            operations,
4413        }
4414    }
4415
4416    /// Parse the operation signatures inside an effect body.
4417    ///
4418    /// Each operation is a `fn` signature without a body.
4419    fn parse_effect_operations(&mut self) -> Vec<FnDecl> {
4420        let mut ops = Vec::new();
4421
4422        loop {
4423            self.skip_newlines();
4424            while self.at(TokenKind::DocComment) {
4425                let _ = self.advance();
4426                self.skip_newlines();
4427            }
4428            if self.at(TokenKind::RBrace) || self.at(TokenKind::Eof) {
4429                break;
4430            }
4431
4432            // Leading annotations.
4433            let mut annotations = Vec::new();
4434            while self.at(TokenKind::At) {
4435                annotations.push(self.parse_annotation());
4436                self.skip_newlines();
4437            }
4438
4439            let vis = if self.at_visibility() {
4440                self.parse_visibility()
4441            } else {
4442                Visibility::Private
4443            };
4444
4445            if !matches!(self.peek().kind, TokenKind::Fn | TokenKind::Async) {
4446                if self.at(TokenKind::Eof) {
4447                    break;
4448                }
4449                let _ = self.advance();
4450                continue;
4451            }
4452
4453            let fn_start = self.peek().span;
4454            let is_async = if self.at(TokenKind::Async) {
4455                let _ = self.advance();
4456                true
4457            } else {
4458                false
4459            };
4460            let _ = self.expect(TokenKind::Fn);
4461
4462            let fn_name_span = self.peek().span;
4463            let fn_name = if self.at(TokenKind::Ident) {
4464                let tok = self.advance();
4465                Ident {
4466                    name: tok.literal.unwrap_or_default(),
4467                    span: tok.span,
4468                }
4469            } else {
4470                self.diagnostics.error(
4471                    DiagnosticCode {
4472                        prefix: 'E',
4473                        number: 2091,
4474                    },
4475                    format!("expected operation name, found `{}`", self.peek().kind),
4476                    fn_name_span,
4477                );
4478                Ident {
4479                    name: String::new(),
4480                    span: fn_name_span,
4481                }
4482            };
4483
4484            let generic_params = self.parse_generic_params();
4485            let _ = self.expect(TokenKind::LParen);
4486            let params = self.parse_param_list();
4487            let _ = self.expect(TokenKind::RParen);
4488
4489            let return_type = if self.at(TokenKind::ThinArrow) {
4490                let _ = self.advance();
4491                Some(self.parse_type_expr())
4492            } else {
4493                None
4494            };
4495
4496            let effect_clause = self.parse_effect_clause();
4497            let where_clause = self.parse_where_clause();
4498
4499            // Operations have no body.
4500            let fn_end = self.peek().span;
4501
4502            ops.push(FnDecl {
4503                id: self.alloc_id(),
4504                span: Span::merge(fn_start, fn_end),
4505                annotations,
4506                visibility: vis,
4507                is_async,
4508                name: fn_name,
4509                generic_params,
4510                params,
4511                return_type,
4512                effect_clause,
4513                where_clause,
4514                body: None,
4515            });
4516        }
4517
4518        ops
4519    }
4520
4521    // ─── Module handle declarations ───────────────────────────────────────────
4522
4523    /// Parse a module-level `handle TypePath with expr NEWLINE`.
4524    fn parse_module_handle_decl(&mut self) -> ModuleHandleDecl {
4525        let start = self.peek().span;
4526        let _ = self.advance(); // consume `handle`
4527
4528        let effect = self.parse_type_path();
4529        let _ = self.expect(TokenKind::With);
4530        let handler = self.parse_expr();
4531        let end = handler.span();
4532
4533        if self.at(TokenKind::Newline) {
4534            let _ = self.advance();
4535        }
4536
4537        ModuleHandleDecl {
4538            id: self.alloc_id(),
4539            span: Span::merge(start, end),
4540            effect,
4541            handler,
4542        }
4543    }
4544}
4545
4546// ─── Tests ───────────────────────────────────────────────────────────────────
4547
4548#[cfg(test)]
4549mod tests {
4550    use super::*;
4551    use bock_errors::FileId;
4552    use bock_lexer::Lexer;
4553    use std::path::PathBuf;
4554
4555    fn parse(src: &str) -> (Module, DiagnosticBag) {
4556        let file_id = FileId(0);
4557        let source = SourceFile::new(file_id, PathBuf::from("test.bock"), src.to_string());
4558        let tokens = Lexer::new(&source).tokenize();
4559        let mut parser = Parser::new(tokens, &source);
4560        let module = parser.parse_module();
4561        let diags = std::mem::replace(&mut parser.diagnostics, DiagnosticBag::new());
4562        (module, diags)
4563    }
4564
4565    #[test]
4566    fn parse_empty_file() {
4567        let (m, diags) = parse("");
4568        assert!(m.path.is_none());
4569        assert!(m.imports.is_empty());
4570        assert!(m.items.is_empty());
4571        assert!(!diags.has_errors());
4572    }
4573
4574    #[test]
4575    fn parse_module_declaration() {
4576        let (m, diags) = parse("module app.auth\n");
4577        assert!(!diags.has_errors(), "unexpected errors");
4578        let path = m.path.expect("module decl should be present");
4579        let names: Vec<&str> = path.segments.iter().map(|s| s.name.as_str()).collect();
4580        assert_eq!(names, ["app", "auth"]);
4581    }
4582
4583    #[test]
4584    fn parse_module_declaration_missing() {
4585        // A file without `module` declaration is valid — path is None.
4586        let (m, diags) = parse("fn foo() {}\n");
4587        assert!(m.path.is_none());
4588        assert!(!diags.has_errors());
4589    }
4590
4591    #[test]
4592    fn parse_import_glob() {
4593        let (m, diags) = parse("use app.services.*\n");
4594        assert!(!diags.has_errors(), "unexpected errors");
4595        assert_eq!(m.imports.len(), 1);
4596        let imp = &m.imports[0];
4597        let path_segs: Vec<&str> = imp.path.segments.iter().map(|s| s.name.as_str()).collect();
4598        assert_eq!(path_segs, ["app", "services"]);
4599        assert_eq!(imp.items, ImportItems::Glob);
4600    }
4601
4602    #[test]
4603    fn parse_import_named_list() {
4604        let (m, diags) = parse("use core.collections.{List, Map}\n");
4605        assert!(!diags.has_errors(), "unexpected errors");
4606        assert_eq!(m.imports.len(), 1);
4607        let imp = &m.imports[0];
4608        let path_segs: Vec<&str> = imp.path.segments.iter().map(|s| s.name.as_str()).collect();
4609        assert_eq!(path_segs, ["core", "collections"]);
4610        match &imp.items {
4611            ImportItems::Named(names) => {
4612                let ns: Vec<&str> = names.iter().map(|n| n.name.name.as_str()).collect();
4613                assert_eq!(ns, ["List", "Map"]);
4614            }
4615            other => panic!("expected Named, got {other:?}"),
4616        }
4617    }
4618
4619    #[test]
4620    fn parse_module_path_with_effect_keyword_segment() {
4621        // §18.3 names the stdlib module `core.effect`; the `effect` reserved
4622        // keyword must be accepted as a module-path segment.
4623        let (m, diags) = parse("module core.effect\n");
4624        assert!(!diags.has_errors(), "unexpected errors: {diags:?}");
4625        let path = m.path.expect("module decl should be present");
4626        let names: Vec<&str> = path.segments.iter().map(|s| s.name.as_str()).collect();
4627        assert_eq!(names, ["core", "effect"]);
4628    }
4629
4630    #[test]
4631    fn parse_module_path_with_handle_and_handling_segments() {
4632        // For symmetry, the other effect-family keywords are also valid segments.
4633        let (m, diags) = parse("module core.handle.handling\n");
4634        assert!(!diags.has_errors(), "unexpected errors: {diags:?}");
4635        let path = m.path.expect("module decl should be present");
4636        let names: Vec<&str> = path.segments.iter().map(|s| s.name.as_str()).collect();
4637        assert_eq!(names, ["core", "handle", "handling"]);
4638    }
4639
4640    #[test]
4641    fn parse_import_named_list_with_effect_keyword_segment() {
4642        // `use core.effect.{Log, console_log}` — the `effect` keyword segment must
4643        // not swallow or desync the `.{...}` import list.
4644        let (m, diags) = parse("use core.effect.{Log, console_log}\n");
4645        assert!(!diags.has_errors(), "unexpected errors: {diags:?}");
4646        assert_eq!(m.imports.len(), 1);
4647        let imp = &m.imports[0];
4648        let path_segs: Vec<&str> = imp.path.segments.iter().map(|s| s.name.as_str()).collect();
4649        assert_eq!(path_segs, ["core", "effect"]);
4650        match &imp.items {
4651            ImportItems::Named(names) => {
4652                let ns: Vec<&str> = names.iter().map(|n| n.name.name.as_str()).collect();
4653                assert_eq!(ns, ["Log", "console_log"]);
4654            }
4655            other => panic!("expected Named, got {other:?}"),
4656        }
4657    }
4658
4659    #[test]
4660    fn effect_keyword_still_parses_as_effect_declaration_at_item_position() {
4661        // Regression: the path-segment relaxation must NOT turn an item-position
4662        // `effect Log { ... }` into a path. It must still parse as an effect decl.
4663        let (m, diags) = parse("effect Log {\n  fn log() -> Void\n}\n");
4664        assert!(!diags.has_errors(), "unexpected errors: {diags:?}");
4665        assert!(m.path.is_none(), "no module decl expected");
4666        assert_eq!(m.items.len(), 1);
4667        let Item::Effect(e) = &m.items[0] else {
4668            panic!("expected Item::Effect, got {:?}", m.items[0]);
4669        };
4670        assert_eq!(e.name.name, "Log");
4671        assert_eq!(e.operations.len(), 1);
4672        assert_eq!(e.operations[0].name.name, "log");
4673    }
4674
4675    #[test]
4676    fn parse_import_single_name() {
4677        let (m, diags) = parse("use app.models.User\n");
4678        assert!(!diags.has_errors(), "unexpected errors");
4679        assert_eq!(m.imports.len(), 1);
4680        let imp = &m.imports[0];
4681        // With greedy path parsing, `User` is absorbed into the path and items = Module.
4682        // OR `User` is parsed as a single named import. Both are acceptable; verify consistency.
4683        // This test documents the actual behaviour.
4684        match &imp.items {
4685            ImportItems::Named(names) if names.len() == 1 => {
4686                assert_eq!(names[0].name.name, "User");
4687            }
4688            ImportItems::Module => {
4689                // Path ends with `User` — also valid per grammar.
4690                let last = imp.path.segments.last().expect("non-empty path");
4691                assert_eq!(last.name, "User");
4692            }
4693            other => panic!("unexpected import items: {other:?}"),
4694        }
4695    }
4696
4697    #[test]
4698    fn parse_multi_import_file() {
4699        let src = "\
4700module myapp.core\n\
4701use core.collections.{List, Map}\n\
4702use app.models.User\n\
4703use app.services.*\n\
4704";
4705        let (m, diags) = parse(src);
4706        assert!(!diags.has_errors(), "unexpected errors");
4707
4708        // Module declaration
4709        let path = m.path.expect("module decl");
4710        assert_eq!(path.segments[0].name, "myapp");
4711        assert_eq!(path.segments[1].name, "core");
4712
4713        // Three imports
4714        assert_eq!(m.imports.len(), 3);
4715
4716        // First: named list
4717        let first = &m.imports[0];
4718        assert!(matches!(first.items, ImportItems::Named(_)));
4719
4720        // Last: glob
4721        let last = &m.imports[2];
4722        assert_eq!(last.items, ImportItems::Glob);
4723    }
4724
4725    #[test]
4726    fn parser_new_and_diagnostics() {
4727        let file_id = FileId(0);
4728        let source = SourceFile::new(file_id, PathBuf::from("x.bock"), String::new());
4729        let tokens = Lexer::new(&source).tokenize();
4730        let parser = Parser::new(tokens, &source);
4731        assert!(!parser.diagnostics().has_errors());
4732    }
4733
4734    // ── P2.3: Function declaration tests ─────────────────────────────────────
4735
4736    #[test]
4737    fn parse_simple_fn() {
4738        let (m, diags) = parse("fn greet() {}\n");
4739        assert!(!diags.has_errors(), "{diags:?}");
4740        assert_eq!(m.items.len(), 1);
4741        let Item::Fn(f) = &m.items[0] else {
4742            panic!("expected Fn")
4743        };
4744        assert_eq!(f.name.name, "greet");
4745        assert!(!f.is_async);
4746        assert_eq!(f.visibility, Visibility::Private);
4747        assert!(f.params.is_empty());
4748        assert!(f.return_type.is_none());
4749    }
4750
4751    #[test]
4752    fn parse_fn_with_params_and_return_type() {
4753        let (m, diags) = parse("fn add(x: Int, y: Int) -> Int {}\n");
4754        assert!(!diags.has_errors(), "{diags:?}");
4755        let Item::Fn(f) = &m.items[0] else { panic!() };
4756        assert_eq!(f.name.name, "add");
4757        assert_eq!(f.params.len(), 2);
4758        assert_eq!(
4759            f.params[0].pattern,
4760            Pattern::Bind {
4761                id: f.params[0].pattern.node_id(),
4762                span: f.params[0].pattern.span(),
4763                name: Ident {
4764                    name: "x".into(),
4765                    span: f.params[0].pattern.span()
4766                },
4767            }
4768        );
4769        assert!(f.return_type.is_some());
4770        let TypeExpr::Named { path, .. } = f.return_type.as_ref().unwrap() else {
4771            panic!()
4772        };
4773        assert_eq!(path.segments[0].name, "Int");
4774    }
4775
4776    #[test]
4777    fn parse_async_fn() {
4778        let (m, diags) = parse("async fn fetch() -> String {}\n");
4779        assert!(!diags.has_errors(), "{diags:?}");
4780        let Item::Fn(f) = &m.items[0] else { panic!() };
4781        assert!(f.is_async);
4782        assert_eq!(f.name.name, "fetch");
4783    }
4784
4785    #[test]
4786    fn parse_fn_with_visibility() {
4787        let (m, diags) = parse("public fn exposed() {}\n");
4788        assert!(!diags.has_errors(), "{diags:?}");
4789        let Item::Fn(f) = &m.items[0] else { panic!() };
4790        assert_eq!(f.visibility, Visibility::Public);
4791    }
4792
4793    #[test]
4794    fn parse_fn_with_generic_params() {
4795        let (m, diags) = parse("fn identity[T](x: T) -> T {}\n");
4796        assert!(!diags.has_errors(), "{diags:?}");
4797        let Item::Fn(f) = &m.items[0] else { panic!() };
4798        assert_eq!(f.generic_params.len(), 1);
4799        assert_eq!(f.generic_params[0].name.name, "T");
4800    }
4801
4802    #[test]
4803    fn parse_fn_with_generic_bounds() {
4804        let (m, diags) = parse("fn compare[T: Ord](a: T, b: T) -> Bool {}\n");
4805        assert!(!diags.has_errors(), "{diags:?}");
4806        let Item::Fn(f) = &m.items[0] else { panic!() };
4807        assert_eq!(f.generic_params[0].name.name, "T");
4808        assert_eq!(f.generic_params[0].bounds[0].segments[0].name, "Ord");
4809    }
4810
4811    #[test]
4812    fn parse_fn_with_where_clause() {
4813        let (m, diags) = parse("fn sorted[T](items: List) -> List where (T: Ord) {}\n");
4814        assert!(!diags.has_errors(), "{diags:?}");
4815        let Item::Fn(f) = &m.items[0] else { panic!() };
4816        assert_eq!(f.where_clause.len(), 1);
4817        assert_eq!(f.where_clause[0].param.name, "T");
4818        assert_eq!(f.where_clause[0].bounds[0].segments[0].name, "Ord");
4819    }
4820
4821    #[test]
4822    fn parse_fn_with_where_clause_multiple_constraints() {
4823        let (m, diags) = parse("fn dual[T, U](a: T, b: U) -> Bool where (T: Eq, U: Ord) {}\n");
4824        assert!(!diags.has_errors(), "{diags:?}");
4825        let Item::Fn(f) = &m.items[0] else { panic!() };
4826        assert_eq!(f.where_clause.len(), 2);
4827    }
4828
4829    #[test]
4830    fn parse_fn_with_effect_clause() {
4831        let (m, diags) = parse("fn log_msg(msg: String) with Log {}\n");
4832        assert!(!diags.has_errors(), "{diags:?}");
4833        let Item::Fn(f) = &m.items[0] else { panic!() };
4834        assert_eq!(f.effect_clause.len(), 1);
4835        assert_eq!(f.effect_clause[0].segments[0].name, "Log");
4836    }
4837
4838    #[test]
4839    fn parse_fn_with_multiple_effects() {
4840        let (m, diags) = parse("fn do_io(path: String) with Io, Log {}\n");
4841        assert!(!diags.has_errors(), "{diags:?}");
4842        let Item::Fn(f) = &m.items[0] else { panic!() };
4843        assert_eq!(f.effect_clause.len(), 2);
4844    }
4845
4846    #[test]
4847    fn parse_fn_with_default_param() {
4848        let (m, diags) = parse("fn greet(name: String, loud: Bool = false) {}\n");
4849        assert!(!diags.has_errors(), "{diags:?}");
4850        let Item::Fn(f) = &m.items[0] else { panic!() };
4851        assert!(f.params[1].default.is_some());
4852        let Expr::Literal {
4853            lit: Literal::Bool(false),
4854            ..
4855        } = f.params[1].default.as_ref().unwrap()
4856        else {
4857            panic!("expected false literal")
4858        };
4859    }
4860
4861    #[test]
4862    fn parse_fn_with_annotation() {
4863        let (m, diags) = parse("@test\nfn it_works() {}\n");
4864        assert!(!diags.has_errors(), "{diags:?}");
4865        let Item::Fn(f) = &m.items[0] else { panic!() };
4866        assert_eq!(f.annotations.len(), 1);
4867        assert_eq!(f.annotations[0].name.name, "test");
4868    }
4869
4870    // ── P2.3: Record declaration tests ───────────────────────────────────────
4871
4872    #[test]
4873    fn parse_simple_record() {
4874        let (m, diags) = parse("record Point { x: Int, y: Int }\n");
4875        assert!(!diags.has_errors(), "{diags:?}");
4876        assert_eq!(m.items.len(), 1);
4877        let Item::Record(r) = &m.items[0] else {
4878            panic!("expected Record")
4879        };
4880        assert_eq!(r.name.name, "Point");
4881        assert_eq!(r.fields.len(), 2);
4882        assert_eq!(r.fields[0].name.name, "x");
4883        assert_eq!(r.fields[1].name.name, "y");
4884    }
4885
4886    #[test]
4887    fn parse_record_with_default_field_values() {
4888        let (m, diags) = parse("record Config { retries: Int = 3, verbose: Bool = false }\n");
4889        assert!(!diags.has_errors(), "{diags:?}");
4890        let Item::Record(r) = &m.items[0] else {
4891            panic!()
4892        };
4893        assert_eq!(r.name.name, "Config");
4894        assert!(r.fields[0].default.is_some());
4895        let Expr::Literal {
4896            lit: Literal::Int(n),
4897            ..
4898        } = r.fields[0].default.as_ref().unwrap()
4899        else {
4900            panic!("expected int literal")
4901        };
4902        assert_eq!(n, "3");
4903        assert!(r.fields[1].default.is_some());
4904    }
4905
4906    #[test]
4907    fn parse_record_with_generic_params() {
4908        let (m, diags) = parse("record Pair[A, B] { first: A, second: B }\n");
4909        assert!(!diags.has_errors(), "{diags:?}");
4910        let Item::Record(r) = &m.items[0] else {
4911            panic!()
4912        };
4913        assert_eq!(r.generic_params.len(), 2);
4914        assert_eq!(r.generic_params[0].name.name, "A");
4915        assert_eq!(r.generic_params[1].name.name, "B");
4916    }
4917
4918    #[test]
4919    fn parse_record_with_annotation() {
4920        let (m, diags) = parse("@derive(Equatable)\nrecord User { id: Int, name: String }\n");
4921        assert!(!diags.has_errors(), "{diags:?}");
4922        let Item::Record(r) = &m.items[0] else {
4923            panic!()
4924        };
4925        assert_eq!(r.annotations.len(), 1);
4926        assert_eq!(r.annotations[0].name.name, "derive");
4927    }
4928
4929    #[test]
4930    fn parse_record_with_visibility() {
4931        let (m, diags) = parse("public record Token { kind: Int }\n");
4932        assert!(!diags.has_errors(), "{diags:?}");
4933        let Item::Record(r) = &m.items[0] else {
4934            panic!()
4935        };
4936        assert_eq!(r.visibility, Visibility::Public);
4937    }
4938
4939    #[test]
4940    fn parse_record_optional_field_type() {
4941        let (m, diags) = parse("record Profile { name: String, bio: String? }\n");
4942        assert!(!diags.has_errors(), "{diags:?}");
4943        let Item::Record(r) = &m.items[0] else {
4944            panic!()
4945        };
4946        assert!(matches!(r.fields[1].ty, TypeExpr::Optional { .. }));
4947    }
4948
4949    // ── P2.3: Enum declaration tests ─────────────────────────────────────────
4950
4951    #[test]
4952    fn parse_enum_unit_variants() {
4953        let (m, diags) = parse("enum Direction {\n  North\n  South\n  East\n  West\n}\n");
4954        assert!(!diags.has_errors(), "{diags:?}");
4955        let Item::Enum(e) = &m.items[0] else {
4956            panic!("expected Enum")
4957        };
4958        assert_eq!(e.name.name, "Direction");
4959        assert_eq!(e.variants.len(), 4);
4960        for v in &e.variants {
4961            assert!(matches!(v, EnumVariant::Unit { .. }));
4962        }
4963    }
4964
4965    #[test]
4966    fn parse_enum_struct_variants() {
4967        let src = "enum Shape {\n  Circle { radius: Float }\n  Rect { w: Float, h: Float }\n}\n";
4968        let (m, diags) = parse(src);
4969        assert!(!diags.has_errors(), "{diags:?}");
4970        let Item::Enum(e) = &m.items[0] else { panic!() };
4971        assert_eq!(e.variants.len(), 2);
4972        assert!(matches!(&e.variants[0], EnumVariant::Struct { .. }));
4973        assert!(matches!(&e.variants[1], EnumVariant::Struct { .. }));
4974        let EnumVariant::Struct { fields, .. } = &e.variants[0] else {
4975            unreachable!()
4976        };
4977        assert_eq!(fields[0].name.name, "radius");
4978    }
4979
4980    #[test]
4981    fn parse_enum_tuple_variants() {
4982        let src = "enum Result {\n  Ok(Int)\n  Err(String)\n}\n";
4983        let (m, diags) = parse(src);
4984        assert!(!diags.has_errors(), "{diags:?}");
4985        let Item::Enum(e) = &m.items[0] else { panic!() };
4986        assert_eq!(e.variants.len(), 2);
4987        assert!(matches!(&e.variants[0], EnumVariant::Tuple { .. }));
4988        assert!(matches!(&e.variants[1], EnumVariant::Tuple { .. }));
4989        let EnumVariant::Tuple { tys, .. } = &e.variants[0] else {
4990            unreachable!()
4991        };
4992        assert_eq!(tys.len(), 1);
4993    }
4994
4995    #[test]
4996    fn parse_enum_mixed_variants() {
4997        let src = "enum Expr {\n  Num(Int)\n  Add { left: Int, right: Int }\n  Unit\n}\n";
4998        let (m, diags) = parse(src);
4999        assert!(!diags.has_errors(), "{diags:?}");
5000        let Item::Enum(e) = &m.items[0] else { panic!() };
5001        assert_eq!(e.variants.len(), 3);
5002        assert!(matches!(&e.variants[0], EnumVariant::Tuple { .. }));
5003        assert!(matches!(&e.variants[1], EnumVariant::Struct { .. }));
5004        assert!(matches!(&e.variants[2], EnumVariant::Unit { .. }));
5005    }
5006
5007    #[test]
5008    fn parse_enum_with_generics() {
5009        let src = "enum Option[T] {\n  Some(T)\n  None\n}\n";
5010        let (m, diags) = parse(src);
5011        assert!(!diags.has_errors(), "{diags:?}");
5012        let Item::Enum(e) = &m.items[0] else { panic!() };
5013        assert_eq!(e.generic_params.len(), 1);
5014        assert_eq!(e.generic_params[0].name.name, "T");
5015        assert_eq!(e.variants.len(), 2);
5016    }
5017
5018    #[test]
5019    fn parse_enum_with_annotation() {
5020        let src = "@derive(Equatable)\nenum Status {\n  Active\n  Inactive\n}\n";
5021        let (m, diags) = parse(src);
5022        assert!(!diags.has_errors(), "{diags:?}");
5023        let Item::Enum(e) = &m.items[0] else { panic!() };
5024        assert_eq!(e.annotations.len(), 1);
5025    }
5026
5027    // ── P2.3: Type expression tests ───────────────────────────────────────────
5028
5029    #[test]
5030    fn parse_generic_type_in_field() {
5031        let (m, diags) = parse("record Container { items: List[Int] }\n");
5032        assert!(!diags.has_errors(), "{diags:?}");
5033        let Item::Record(r) = &m.items[0] else {
5034            panic!()
5035        };
5036        let TypeExpr::Named { args, .. } = &r.fields[0].ty else {
5037            panic!()
5038        };
5039        assert_eq!(args.len(), 1);
5040    }
5041
5042    #[test]
5043    fn parse_multiple_items_in_file() {
5044        let src = "fn foo() {}\nrecord Bar { x: Int }\nenum Baz { A\n  B\n}\n";
5045        let (m, diags) = parse(src);
5046        assert!(!diags.has_errors(), "{diags:?}");
5047        assert_eq!(m.items.len(), 3);
5048        assert!(matches!(m.items[0], Item::Fn(_)));
5049        assert!(matches!(m.items[1], Item::Record(_)));
5050        assert!(matches!(m.items[2], Item::Enum(_)));
5051    }
5052
5053    // ── P2.4: Class / Trait / Impl tests ─────────────────────────────────────
5054
5055    #[test]
5056    fn parse_empty_class() {
5057        let src = "class Animal {}\n";
5058        let (m, diags) = parse(src);
5059        assert!(!diags.has_errors(), "{diags:?}");
5060        let Item::Class(c) = &m.items[0] else {
5061            panic!("expected Class")
5062        };
5063        assert_eq!(c.name.name, "Animal");
5064        assert!(c.base.is_none());
5065        assert!(c.traits.is_empty());
5066        assert!(c.fields.is_empty());
5067        assert!(c.methods.is_empty());
5068    }
5069
5070    #[test]
5071    fn parse_class_with_fields_and_method() {
5072        let src = "class Point {\n  x: Int\n  y: Int\n  fn distance(self) -> Float { }\n}\n";
5073        let (m, diags) = parse(src);
5074        assert!(!diags.has_errors(), "{diags:?}");
5075        let Item::Class(c) = &m.items[0] else {
5076            panic!()
5077        };
5078        assert_eq!(c.name.name, "Point");
5079        assert_eq!(c.fields.len(), 2);
5080        assert_eq!(c.fields[0].name.name, "x");
5081        assert_eq!(c.fields[1].name.name, "y");
5082        assert_eq!(c.methods.len(), 1);
5083        assert_eq!(c.methods[0].name.name, "distance");
5084    }
5085
5086    #[test]
5087    fn parse_class_with_inheritance() {
5088        let src = "class Dog : Animal, Trainable {}\n";
5089        let (m, diags) = parse(src);
5090        assert!(!diags.has_errors(), "{diags:?}");
5091        let Item::Class(c) = &m.items[0] else {
5092            panic!()
5093        };
5094        assert_eq!(c.name.name, "Dog");
5095        let base = c.base.as_ref().expect("should have base");
5096        assert_eq!(base.segments[0].name, "Animal");
5097        assert_eq!(c.traits.len(), 1);
5098        assert_eq!(c.traits[0].segments[0].name, "Trainable");
5099    }
5100
5101    #[test]
5102    fn parse_class_with_generics() {
5103        let src = "class Box[T] {\n  value: T\n}\n";
5104        let (m, diags) = parse(src);
5105        assert!(!diags.has_errors(), "{diags:?}");
5106        let Item::Class(c) = &m.items[0] else {
5107            panic!()
5108        };
5109        assert_eq!(c.generic_params.len(), 1);
5110        assert_eq!(c.generic_params[0].name.name, "T");
5111        assert_eq!(c.fields.len(), 1);
5112    }
5113
5114    #[test]
5115    fn parse_class_with_annotation() {
5116        let src = "@derive(Equatable)\nclass User {\n  name: String\n}\n";
5117        let (m, diags) = parse(src);
5118        assert!(!diags.has_errors(), "{diags:?}");
5119        let Item::Class(c) = &m.items[0] else {
5120            panic!()
5121        };
5122        assert_eq!(c.annotations.len(), 1);
5123        assert_eq!(c.annotations[0].name.name, "derive");
5124    }
5125
5126    #[test]
5127    fn parse_public_class() {
5128        let src = "public class Foo {}\n";
5129        let (m, diags) = parse(src);
5130        assert!(!diags.has_errors(), "{diags:?}");
5131        let Item::Class(c) = &m.items[0] else {
5132            panic!()
5133        };
5134        assert_eq!(c.visibility, Visibility::Public);
5135    }
5136
5137    #[test]
5138    fn parse_empty_trait() {
5139        let src = "trait Printable {}\n";
5140        let (m, diags) = parse(src);
5141        assert!(!diags.has_errors(), "{diags:?}");
5142        let Item::Trait(t) = &m.items[0] else {
5143            panic!("expected Trait")
5144        };
5145        assert_eq!(t.name.name, "Printable");
5146        assert!(!t.is_platform);
5147        assert!(t.methods.is_empty());
5148        assert!(t.associated_types.is_empty());
5149    }
5150
5151    #[test]
5152    fn parse_trait_with_required_and_default_methods() {
5153        let src = "trait Display {\n  fn show(self) -> String\n  fn debug(self) -> String { }\n}\n";
5154        let (m, diags) = parse(src);
5155        assert!(!diags.has_errors(), "{diags:?}");
5156        let Item::Trait(t) = &m.items[0] else {
5157            panic!()
5158        };
5159        assert_eq!(t.methods.len(), 2);
5160        assert_eq!(t.methods[0].name.name, "show");
5161        assert_eq!(t.methods[1].name.name, "debug");
5162    }
5163
5164    #[test]
5165    fn parse_trait_with_associated_type() {
5166        let src = "trait Collection {\n  type Item\n  fn len(self) -> Int\n}\n";
5167        let (m, diags) = parse(src);
5168        assert!(!diags.has_errors(), "{diags:?}");
5169        let Item::Trait(t) = &m.items[0] else {
5170            panic!()
5171        };
5172        assert_eq!(t.associated_types.len(), 1);
5173        assert_eq!(t.associated_types[0].name.name, "Item");
5174        assert_eq!(t.methods.len(), 1);
5175    }
5176
5177    #[test]
5178    fn parse_trait_associated_type_with_bound() {
5179        let src = "trait Keyed {\n  type Key: Hashable\n}\n";
5180        let (m, diags) = parse(src);
5181        assert!(!diags.has_errors(), "{diags:?}");
5182        let Item::Trait(t) = &m.items[0] else {
5183            panic!()
5184        };
5185        assert_eq!(t.associated_types[0].bounds.len(), 1);
5186        assert_eq!(t.associated_types[0].bounds[0].segments[0].name, "Hashable");
5187    }
5188
5189    #[test]
5190    fn parse_trait_with_generics() {
5191        let src = "trait Functor[F] {\n  fn map(self) -> F\n}\n";
5192        let (m, diags) = parse(src);
5193        assert!(!diags.has_errors(), "{diags:?}");
5194        let Item::Trait(t) = &m.items[0] else {
5195            panic!()
5196        };
5197        assert_eq!(t.generic_params.len(), 1);
5198        assert_eq!(t.generic_params[0].name.name, "F");
5199    }
5200
5201    #[test]
5202    fn parse_platform_trait() {
5203        let src = "platform trait FileSystem {\n  fn read(path: String) -> String\n}\n";
5204        let (m, diags) = parse(src);
5205        assert!(!diags.has_errors(), "{diags:?}");
5206        let Item::PlatformTrait(t) = &m.items[0] else {
5207            panic!("expected PlatformTrait")
5208        };
5209        assert_eq!(t.name.name, "FileSystem");
5210        assert!(t.is_platform);
5211        assert_eq!(t.methods.len(), 1);
5212    }
5213
5214    #[test]
5215    fn parse_impl_type() {
5216        let src = "impl Dog {\n  fn bark(self) -> String { }\n}\n";
5217        let (m, diags) = parse(src);
5218        assert!(!diags.has_errors(), "{diags:?}");
5219        let Item::Impl(b) = &m.items[0] else {
5220            panic!("expected Impl")
5221        };
5222        assert!(b.trait_path.is_none());
5223        assert_eq!(b.methods.len(), 1);
5224        assert_eq!(b.methods[0].name.name, "bark");
5225    }
5226
5227    #[test]
5228    fn parse_impl_trait_for_type() {
5229        let src = "impl Printable for Dog {\n  fn show(self) -> String { }\n}\n";
5230        let (m, diags) = parse(src);
5231        assert!(!diags.has_errors(), "{diags:?}");
5232        let Item::Impl(b) = &m.items[0] else { panic!() };
5233        let trait_path = b.trait_path.as_ref().expect("should have trait path");
5234        assert_eq!(trait_path.segments[0].name, "Printable");
5235        assert_eq!(b.methods.len(), 1);
5236    }
5237
5238    #[test]
5239    fn parse_impl_with_generics() {
5240        let src = "impl[T] Display for Box[T] {\n  fn show(self) -> String { }\n}\n";
5241        let (m, diags) = parse(src);
5242        assert!(!diags.has_errors(), "{diags:?}");
5243        let Item::Impl(b) = &m.items[0] else { panic!() };
5244        assert_eq!(b.generic_params.len(), 1);
5245        assert_eq!(b.generic_params[0].name.name, "T");
5246        let trait_path = b.trait_path.as_ref().expect("trait path");
5247        assert_eq!(trait_path.segments[0].name, "Display");
5248    }
5249
5250    #[test]
5251    fn parse_impl_empty_body() {
5252        let src = "impl Animal {}\n";
5253        let (m, diags) = parse(src);
5254        assert!(!diags.has_errors(), "{diags:?}");
5255        let Item::Impl(b) = &m.items[0] else { panic!() };
5256        assert!(b.methods.is_empty());
5257    }
5258
5259    #[test]
5260    fn parse_mixed_items_with_class_trait_impl() {
5261        let src = concat!(
5262            "trait Greet {\n  fn hello(self) -> String\n}\n",
5263            "class Cat {}\n",
5264            "impl Greet for Cat {\n  fn hello(self) -> String { }\n}\n",
5265        );
5266        let (m, diags) = parse(src);
5267        assert!(!diags.has_errors(), "{diags:?}");
5268        assert_eq!(m.items.len(), 3);
5269        assert!(matches!(m.items[0], Item::Trait(_)));
5270        assert!(matches!(m.items[1], Item::Class(_)));
5271        assert!(matches!(m.items[2], Item::Impl(_)));
5272    }
5273
5274    // ─── Expression parsing tests (P2.5) ─────────────────────────────────────
5275
5276    /// Helper to parse a function body and extract the tail expression.
5277    fn parse_fn_body(src: &str) -> (Expr, DiagnosticBag) {
5278        let src = format!("fn test() {{\n{src}\n}}");
5279        let (m, diags) = parse(&src);
5280        let fn_decl = match m.items.first().expect("expected fn") {
5281            Item::Fn(f) => f.clone(),
5282            _ => panic!("expected fn item"),
5283        };
5284        let body = fn_decl.body.expect("expected function body");
5285        let tail = body.tail.expect("expected tail expression");
5286        (*tail, diags)
5287    }
5288
5289    /// Helper to parse an expression directly (no fn wrapper).
5290    fn parse_expr_str(src: &str) -> (Expr, DiagnosticBag) {
5291        parse_fn_body(src)
5292    }
5293
5294    #[test]
5295    fn expr_integer_literal() {
5296        let (e, diags) = parse_expr_str("42");
5297        assert!(!diags.has_errors(), "{diags:?}");
5298        assert!(matches!(e, Expr::Literal { lit: Literal::Int(ref s), .. } if s == "42"));
5299    }
5300
5301    #[test]
5302    fn expr_float_literal() {
5303        let (e, diags) = parse_expr_str("3.14");
5304        assert!(!diags.has_errors(), "{diags:?}");
5305        assert!(matches!(
5306            e,
5307            Expr::Literal {
5308                lit: Literal::Float(_),
5309                ..
5310            }
5311        ));
5312    }
5313
5314    #[test]
5315    fn expr_bool_literal() {
5316        let (e, diags) = parse_expr_str("true");
5317        assert!(!diags.has_errors(), "{diags:?}");
5318        assert!(matches!(
5319            e,
5320            Expr::Literal {
5321                lit: Literal::Bool(true),
5322                ..
5323            }
5324        ));
5325
5326        let (e2, _) = parse_expr_str("false");
5327        assert!(matches!(
5328            e2,
5329            Expr::Literal {
5330                lit: Literal::Bool(false),
5331                ..
5332            }
5333        ));
5334    }
5335
5336    #[test]
5337    fn expr_string_literal() {
5338        let (e, diags) = parse_expr_str(r#""hello""#);
5339        assert!(!diags.has_errors(), "{diags:?}");
5340        assert!(matches!(
5341            e,
5342            Expr::Literal {
5343                lit: Literal::String(_),
5344                ..
5345            }
5346        ));
5347    }
5348
5349    #[test]
5350    fn expr_identifier() {
5351        let (e, diags) = parse_expr_str("foo");
5352        assert!(!diags.has_errors(), "{diags:?}");
5353        assert!(matches!(e, Expr::Identifier { ref name, .. } if name.name == "foo"));
5354    }
5355
5356    #[test]
5357    fn expr_binary_add() {
5358        let (e, diags) = parse_expr_str("1 + 2");
5359        assert!(!diags.has_errors(), "{diags:?}");
5360        assert!(matches!(e, Expr::Binary { op: BinOp::Add, .. }));
5361    }
5362
5363    #[test]
5364    fn expr_binary_precedence_mul_over_add() {
5365        // `1 + 2 * 3` should parse as `1 + (2 * 3)`
5366        let (e, diags) = parse_expr_str("1 + 2 * 3");
5367        assert!(!diags.has_errors(), "{diags:?}");
5368        match e {
5369            Expr::Binary {
5370                op: BinOp::Add,
5371                right,
5372                ..
5373            } => {
5374                assert!(matches!(*right, Expr::Binary { op: BinOp::Mul, .. }));
5375            }
5376            _ => panic!("expected Add binary expr, got {e:?}"),
5377        }
5378    }
5379
5380    #[test]
5381    fn expr_binary_left_associative() {
5382        // `1 - 2 - 3` should parse as `(1 - 2) - 3`
5383        let (e, diags) = parse_expr_str("1 - 2 - 3");
5384        assert!(!diags.has_errors(), "{diags:?}");
5385        match e {
5386            Expr::Binary {
5387                op: BinOp::Sub,
5388                left,
5389                right,
5390                ..
5391            } => {
5392                assert!(matches!(*left, Expr::Binary { op: BinOp::Sub, .. }));
5393                assert!(matches!(*right, Expr::Literal { .. }));
5394            }
5395            _ => panic!("expected Sub binary expr"),
5396        }
5397    }
5398
5399    #[test]
5400    fn expr_power_right_associative() {
5401        // `2 ** 3 ** 4` should parse as `2 ** (3 ** 4)`
5402        let (e, diags) = parse_expr_str("2 ** 3 ** 4");
5403        assert!(!diags.has_errors(), "{diags:?}");
5404        match e {
5405            Expr::Binary {
5406                op: BinOp::Pow,
5407                left,
5408                right,
5409                ..
5410            } => {
5411                assert!(matches!(*left, Expr::Literal { .. }));
5412                assert!(matches!(*right, Expr::Binary { op: BinOp::Pow, .. }));
5413            }
5414            _ => panic!("expected Pow binary expr"),
5415        }
5416    }
5417
5418    #[test]
5419    fn expr_comparison_operators() {
5420        for (src, expected_op) in [
5421            ("a == b", BinOp::Eq),
5422            ("a != b", BinOp::Ne),
5423            ("a < b", BinOp::Lt),
5424            ("a > b", BinOp::Gt),
5425            ("a <= b", BinOp::Le),
5426            ("a >= b", BinOp::Ge),
5427        ] {
5428            let (e, diags) = parse_expr_str(src);
5429            assert!(!diags.has_errors(), "errors for {src}: {diags:?}");
5430            assert!(
5431                matches!(&e, Expr::Binary { op, .. } if *op == expected_op),
5432                "{src} expected {expected_op:?}"
5433            );
5434        }
5435    }
5436
5437    #[test]
5438    fn expr_logical_and_or() {
5439        let (e, diags) = parse_expr_str("a && b");
5440        assert!(!diags.has_errors(), "{diags:?}");
5441        assert!(matches!(e, Expr::Binary { op: BinOp::And, .. }));
5442
5443        let (e2, _) = parse_expr_str("a || b");
5444        assert!(matches!(e2, Expr::Binary { op: BinOp::Or, .. }));
5445    }
5446
5447    #[test]
5448    fn expr_and_binds_tighter_than_or() {
5449        // `a || b && c` should parse as `a || (b && c)`
5450        let (e, diags) = parse_expr_str("a || b && c");
5451        assert!(!diags.has_errors(), "{diags:?}");
5452        match e {
5453            Expr::Binary {
5454                op: BinOp::Or,
5455                right,
5456                ..
5457            } => {
5458                assert!(matches!(*right, Expr::Binary { op: BinOp::And, .. }));
5459            }
5460            _ => panic!("expected Or expr"),
5461        }
5462    }
5463
5464    #[test]
5465    fn expr_assignment() {
5466        let (e, diags) = parse_expr_str("x = 5");
5467        assert!(!diags.has_errors(), "{diags:?}");
5468        assert!(matches!(
5469            e,
5470            Expr::Assign {
5471                op: AssignOp::Assign,
5472                ..
5473            }
5474        ));
5475    }
5476
5477    #[test]
5478    fn expr_compound_assignment() {
5479        for (src, expected_op) in [
5480            ("x += 1", AssignOp::AddAssign),
5481            ("x -= 1", AssignOp::SubAssign),
5482            ("x *= 2", AssignOp::MulAssign),
5483            ("x /= 2", AssignOp::DivAssign),
5484            ("x %= 3", AssignOp::RemAssign),
5485        ] {
5486            let (e, diags) = parse_expr_str(src);
5487            assert!(!diags.has_errors(), "errors for {src}: {diags:?}");
5488            assert!(
5489                matches!(&e, Expr::Assign { op, .. } if *op == expected_op),
5490                "expected {expected_op:?} for {src}"
5491            );
5492        }
5493    }
5494
5495    #[test]
5496    fn expr_unary_neg() {
5497        let (e, diags) = parse_expr_str("-x");
5498        assert!(!diags.has_errors(), "{diags:?}");
5499        assert!(matches!(
5500            e,
5501            Expr::Unary {
5502                op: UnaryOp::Neg,
5503                ..
5504            }
5505        ));
5506    }
5507
5508    #[test]
5509    fn expr_unary_not() {
5510        let (e, diags) = parse_expr_str("!flag");
5511        assert!(!diags.has_errors(), "{diags:?}");
5512        assert!(matches!(
5513            e,
5514            Expr::Unary {
5515                op: UnaryOp::Not,
5516                ..
5517            }
5518        ));
5519    }
5520
5521    #[test]
5522    fn expr_unary_bitnot() {
5523        let (e, diags) = parse_expr_str("~x");
5524        assert!(!diags.has_errors(), "{diags:?}");
5525        assert!(matches!(
5526            e,
5527            Expr::Unary {
5528                op: UnaryOp::BitNot,
5529                ..
5530            }
5531        ));
5532    }
5533
5534    #[test]
5535    fn expr_try_operator() {
5536        let (e, diags) = parse_expr_str("result?");
5537        assert!(!diags.has_errors(), "{diags:?}");
5538        assert!(matches!(e, Expr::Try { .. }));
5539    }
5540
5541    #[test]
5542    fn expr_field_access() {
5543        let (e, diags) = parse_expr_str("obj.field");
5544        assert!(!diags.has_errors(), "{diags:?}");
5545        assert!(matches!(e, Expr::FieldAccess { ref field, .. } if field.name == "field"));
5546    }
5547
5548    #[test]
5549    fn expr_method_call() {
5550        let (e, diags) = parse_expr_str("obj.method(1, 2)");
5551        assert!(!diags.has_errors(), "{diags:?}");
5552        assert!(matches!(e, Expr::MethodCall { ref method, .. } if method.name == "method"));
5553    }
5554
5555    #[test]
5556    fn expr_tuple_index_diagnostic() {
5557        // `t.0` is tuple positional indexing, deferred to v1.x
5558        // (spec §7.6). The parser should emit a specific, actionable
5559        // diagnostic (E2092) that points at destructuring rather than the
5560        // generic "expected expression, found `.`" error (E2020).
5561        let (_e, diags) = parse_expr_str("t.0");
5562        assert!(diags.has_errors(), "expected an error for `t.0`");
5563        let diag = diags
5564            .iter()
5565            .find(|d| d.code.prefix == 'E' && d.code.number == 2092)
5566            .expect("expected an E2092 tuple-index diagnostic");
5567        assert!(
5568            diag.message
5569                .contains("tuple positional indexing `t.0` is not available in v1"),
5570            "unexpected message: {}",
5571            diag.message
5572        );
5573        assert!(
5574            diag.message.contains("destructure with `let (a, b) = t`"),
5575            "diagnostic should suggest destructuring: {}",
5576            diag.message
5577        );
5578        // The old generic error must not also fire for this construct.
5579        assert!(
5580            !diags
5581                .iter()
5582                .any(|d| d.code.prefix == 'E' && d.code.number == 2020),
5583            "should not emit the generic E2020 for `t.0`"
5584        );
5585    }
5586
5587    #[test]
5588    fn expr_tuple_index_recovers_single_error() {
5589        // Recovery: parsing continues past the `.0`, so a larger expression
5590        // like `t.0 + 1` yields exactly one diagnostic (no cascade).
5591        let (_e, diags) = parse_expr_str("t.0 + 1");
5592        assert_eq!(
5593            diags.error_count(),
5594            1,
5595            "expected exactly one error from `t.0 + 1`, got {diags:?}"
5596        );
5597    }
5598
5599    #[test]
5600    fn expr_tuple_index_float_chain_diagnostic() {
5601        // `t.0.0` lexes the trailing part as a float literal after the dot;
5602        // it should still produce the tuple-index diagnostic, not a panic or
5603        // the generic error.
5604        let (_e, diags) = parse_expr_str("t.0.0");
5605        assert!(
5606            diags
5607                .iter()
5608                .any(|d| d.code.prefix == 'E' && d.code.number == 2092),
5609            "expected an E2092 tuple-index diagnostic for `t.0.0`, got {diags:?}"
5610        );
5611    }
5612
5613    #[test]
5614    fn tuple_destructuring_still_parses() {
5615        // Regression guard: the destructuring alternative the diagnostic
5616        // recommends must continue to parse without errors.
5617        let src = "fn test() {\n  let t = (1, 2)\n  let (a, b) = t\n  a\n}";
5618        let (_m, diags) = parse(src);
5619        assert!(!diags.has_errors(), "{diags:?}");
5620    }
5621
5622    #[test]
5623    fn expr_function_call() {
5624        let (e, diags) = parse_expr_str("foo(1, 2, 3)");
5625        assert!(!diags.has_errors(), "{diags:?}");
5626        match e {
5627            Expr::Call { args, .. } => assert_eq!(args.len(), 3),
5628            _ => panic!("expected Call"),
5629        }
5630    }
5631
5632    #[test]
5633    fn expr_labeled_arg() {
5634        let (e, diags) = parse_expr_str("foo(x: 1, y: 2)");
5635        assert!(!diags.has_errors(), "{diags:?}");
5636        match e {
5637            Expr::Call { args, .. } => {
5638                assert_eq!(args.len(), 2);
5639                assert_eq!(args[0].label.as_ref().map(|i| i.name.as_str()), Some("x"));
5640                assert_eq!(args[1].label.as_ref().map(|i| i.name.as_str()), Some("y"));
5641            }
5642            _ => panic!("expected Call"),
5643        }
5644    }
5645
5646    #[test]
5647    fn expr_index_access() {
5648        let (e, diags) = parse_expr_str("arr[0]");
5649        assert!(!diags.has_errors(), "{diags:?}");
5650        assert!(matches!(e, Expr::Index { .. }));
5651    }
5652
5653    #[test]
5654    fn expr_postfix_chain() {
5655        // `obj.method(arg).field[0]?`
5656        let (e, diags) = parse_expr_str("obj.method(arg).field");
5657        assert!(!diags.has_errors(), "{diags:?}");
5658        // outer: FieldAccess
5659        match e {
5660            Expr::FieldAccess {
5661                ref field,
5662                ref object,
5663                ..
5664            } => {
5665                assert_eq!(field.name, "field");
5666                assert!(matches!(**object, Expr::MethodCall { .. }));
5667            }
5668            _ => panic!("expected FieldAccess, got {e:?}"),
5669        }
5670    }
5671
5672    #[test]
5673    fn expr_deep_postfix_chain() {
5674        let (e, diags) = parse_expr_str("a.b(c).d[0]?");
5675        assert!(!diags.has_errors(), "{diags:?}");
5676        assert!(matches!(e, Expr::Try { .. }));
5677    }
5678
5679    #[test]
5680    fn expr_pipe_operator() {
5681        let (e, diags) = parse_expr_str("data |> parse");
5682        assert!(!diags.has_errors(), "{diags:?}");
5683        assert!(matches!(e, Expr::Pipe { .. }));
5684    }
5685
5686    #[test]
5687    fn expr_pipe_chain() {
5688        // `a |> b |> c` should be left-assoc: `(a |> b) |> c`
5689        let (e, diags) = parse_expr_str("a |> b |> c");
5690        assert!(!diags.has_errors(), "{diags:?}");
5691        match e {
5692            Expr::Pipe { left, .. } => {
5693                assert!(matches!(*left, Expr::Pipe { .. }));
5694            }
5695            _ => panic!("expected Pipe"),
5696        }
5697    }
5698
5699    #[test]
5700    fn expr_compose_operator() {
5701        let (e, diags) = parse_expr_str("parse >> validate");
5702        assert!(!diags.has_errors(), "{diags:?}");
5703        assert!(matches!(e, Expr::Compose { .. }));
5704    }
5705
5706    #[test]
5707    fn expr_range_exclusive() {
5708        let (e, diags) = parse_expr_str("1..10");
5709        assert!(!diags.has_errors(), "{diags:?}");
5710        assert!(matches!(
5711            e,
5712            Expr::Range {
5713                inclusive: false,
5714                ..
5715            }
5716        ));
5717    }
5718
5719    #[test]
5720    fn expr_range_inclusive() {
5721        let (e, diags) = parse_expr_str("1..=10");
5722        assert!(!diags.has_errors(), "{diags:?}");
5723        assert!(matches!(
5724            e,
5725            Expr::Range {
5726                inclusive: true,
5727                ..
5728            }
5729        ));
5730    }
5731
5732    #[test]
5733    fn expr_bitwise_operators() {
5734        let cases = [
5735            ("a & b", BinOp::BitAnd),
5736            ("a | b", BinOp::BitOr),
5737            ("a ^ b", BinOp::BitXor),
5738        ];
5739        for (src, op) in cases {
5740            let (e, diags) = parse_expr_str(src);
5741            assert!(!diags.has_errors(), "errors for {src}: {diags:?}");
5742            assert!(matches!(&e, Expr::Binary { op: actual, .. } if *actual == op));
5743        }
5744    }
5745
5746    #[test]
5747    fn shl_is_parse_error() {
5748        // `<<` is not a binary operator; it should not parse as infix.
5749        // The parser will parse `a` then stop at `<<`, leaving it unconsumed.
5750        // We verify it did NOT produce a Binary node with shift.
5751        let (e, _) = parse_expr_str("a << 2");
5752        assert!(
5753            !matches!(&e, Expr::Binary { .. }),
5754            "`<<` must not be parsed as infix binary operator"
5755        );
5756    }
5757
5758    #[test]
5759    fn compose_still_works() {
5760        // `>>` remains function composition
5761        let (e, diags) = parse_expr_str("f >> g");
5762        assert!(!diags.has_errors(), "{diags:?}");
5763        assert!(matches!(e, Expr::Compose { .. }));
5764    }
5765
5766    #[test]
5767    fn precedence_add_mul_after_renumber() {
5768        // a + b * c should parse as a + (b * c) — mul binds tighter than add
5769        let (e, diags) = parse_expr_str("a + b * c");
5770        assert!(!diags.has_errors(), "{diags:?}");
5771        // Top-level should be Add
5772        match &e {
5773            Expr::Binary { op, right, .. } => {
5774                assert_eq!(*op, BinOp::Add);
5775                assert!(
5776                    matches!(right.as_ref(), Expr::Binary { op: inner_op, .. } if *inner_op == BinOp::Mul),
5777                    "right side of Add should be Mul"
5778                );
5779            }
5780            _ => panic!("expected Binary(Add) at top level"),
5781        }
5782    }
5783
5784    #[test]
5785    fn expr_placeholder() {
5786        let (e, diags) = parse_expr_str("_");
5787        assert!(!diags.has_errors(), "{diags:?}");
5788        assert!(matches!(e, Expr::Placeholder { .. }));
5789    }
5790
5791    #[test]
5792    fn expr_list_literal() {
5793        let (e, diags) = parse_expr_str("[1, 2, 3]");
5794        assert!(!diags.has_errors(), "{diags:?}");
5795        match e {
5796            Expr::ListLiteral { elems, .. } => assert_eq!(elems.len(), 3),
5797            _ => panic!("expected ListLiteral"),
5798        }
5799    }
5800
5801    #[test]
5802    fn expr_empty_list() {
5803        let (e, diags) = parse_expr_str("[]");
5804        assert!(!diags.has_errors(), "{diags:?}");
5805        assert!(matches!(e, Expr::ListLiteral { ref elems, .. } if elems.is_empty()));
5806    }
5807
5808    #[test]
5809    fn expr_tuple_literal() {
5810        let (e, diags) = parse_expr_str("(1, 2, 3)");
5811        assert!(!diags.has_errors(), "{diags:?}");
5812        match e {
5813            Expr::TupleLiteral { elems, .. } => assert_eq!(elems.len(), 3),
5814            _ => panic!("expected TupleLiteral, got {e:?}"),
5815        }
5816    }
5817
5818    #[test]
5819    fn expr_unit_literal() {
5820        let (e, diags) = parse_expr_str("()");
5821        assert!(!diags.has_errors(), "{diags:?}");
5822        assert!(matches!(
5823            e,
5824            Expr::Literal {
5825                lit: Literal::Unit,
5826                ..
5827            }
5828        ));
5829    }
5830
5831    #[test]
5832    fn expr_set_literal() {
5833        let (e, diags) = parse_expr_str(r#"#{"a", "b"}"#);
5834        assert!(!diags.has_errors(), "{diags:?}");
5835        match e {
5836            Expr::SetLiteral { elems, .. } => assert_eq!(elems.len(), 2),
5837            _ => panic!("expected SetLiteral"),
5838        }
5839    }
5840
5841    #[test]
5842    fn expr_map_literal() {
5843        let (e, diags) = parse_expr_str(r#"{"key": "value"}"#);
5844        assert!(!diags.has_errors(), "{diags:?}");
5845        match e {
5846            Expr::MapLiteral { entries, .. } => assert_eq!(entries.len(), 1),
5847            _ => panic!("expected MapLiteral, got {e:?}"),
5848        }
5849    }
5850
5851    #[test]
5852    fn empty_map_literal_with_type_annotation() {
5853        // FC-15: `let m: Map[String, Int] = {}` should parse as an empty MapLiteral
5854        let src = "fn test() {\nlet m: Map[String, Int] = {}\n}";
5855        let (m, diags) = parse(src);
5856        assert!(!diags.has_errors(), "{diags:?}");
5857        let f = match m.items.first().unwrap() {
5858            Item::Fn(f) => f,
5859            other => panic!("expected Fn, got {other:?}"),
5860        };
5861        let body = f.body.as_ref().expect("expected body");
5862        let stmt = body.stmts.first().expect("expected a statement");
5863        match stmt {
5864            Stmt::Let(let_stmt) => match &let_stmt.value {
5865                Expr::MapLiteral { entries, .. } => assert!(entries.is_empty()),
5866                other => panic!("expected MapLiteral, got {other:?}"),
5867            },
5868            other => panic!("expected Let, got {other:?}"),
5869        }
5870    }
5871
5872    #[test]
5873    fn empty_braces_without_map_annotation_is_block() {
5874        // `let x = {}` without Map annotation should still be a block
5875        let src = "fn test() {\nlet x = {}\n}";
5876        let (m, diags) = parse(src);
5877        assert!(!diags.has_errors(), "{diags:?}");
5878        let f = match m.items.first().unwrap() {
5879            Item::Fn(f) => f,
5880            other => panic!("expected Fn, got {other:?}"),
5881        };
5882        let body = f.body.as_ref().expect("expected body");
5883        let stmt = body.stmts.first().expect("expected a statement");
5884        match stmt {
5885            Stmt::Let(let_stmt) => assert!(
5886                matches!(&let_stmt.value, Expr::Block { .. }),
5887                "expected Block, got {:?}",
5888                let_stmt.value
5889            ),
5890            other => panic!("expected Let, got {other:?}"),
5891        }
5892    }
5893
5894    #[test]
5895    fn if_empty_block_still_works() {
5896        // `if (cond) {}` should still parse as an if with an empty block
5897        let (e, diags) = parse_expr_str("if (true) {}");
5898        assert!(!diags.has_errors(), "{diags:?}");
5899        assert!(matches!(e, Expr::If { .. }));
5900    }
5901
5902    #[test]
5903    fn expr_block_is_expression() {
5904        let src = "fn test() {\n{ let x = 1\nx }\n}";
5905        let (m, diags) = parse(src);
5906        assert!(!diags.has_errors(), "{diags:?}");
5907        let fn_decl = match m.items.first().unwrap() {
5908            Item::Fn(f) => f,
5909            _ => panic!(),
5910        };
5911        // The body should have a tail that is a Block expression
5912        let body = fn_decl.body.as_ref().expect("expected function body");
5913        let tail = body.tail.as_ref().expect("expected tail");
5914        assert!(matches!(**tail, Expr::Block { .. }));
5915    }
5916
5917    #[test]
5918    fn expr_if_expression() {
5919        let (e, diags) = parse_expr_str("if (cond) { a } else { b }");
5920        assert!(!diags.has_errors(), "{diags:?}");
5921        match e {
5922            Expr::If {
5923                let_pattern,
5924                else_block,
5925                ..
5926            } => {
5927                assert!(let_pattern.is_none());
5928                assert!(else_block.is_some());
5929            }
5930            _ => panic!("expected If expr, got {e:?}"),
5931        }
5932    }
5933
5934    #[test]
5935    fn expr_if_no_else() {
5936        let (e, diags) = parse_expr_str("if (x > 0) { foo() }");
5937        assert!(!diags.has_errors(), "{diags:?}");
5938        assert!(matches!(
5939            e,
5940            Expr::If {
5941                else_block: None,
5942                ..
5943            }
5944        ));
5945    }
5946
5947    #[test]
5948    fn expr_if_let() {
5949        let (e, diags) = parse_expr_str("if (let Some(v) = opt) { v }");
5950        assert!(!diags.has_errors(), "{diags:?}");
5951        match e {
5952            Expr::If {
5953                let_pattern: Some(p),
5954                ..
5955            } => {
5956                assert!(matches!(p, Pattern::Constructor { .. }));
5957            }
5958            _ => panic!("expected if-let, got {e:?}"),
5959        }
5960    }
5961
5962    #[test]
5963    fn expr_if_else_if_chain() {
5964        let (e, diags) = parse_expr_str("if (a) { 1 } else if (b) { 2 } else { 3 }");
5965        assert!(!diags.has_errors(), "{diags:?}");
5966        match e {
5967            Expr::If {
5968                else_block: Some(else_e),
5969                ..
5970            } => {
5971                assert!(matches!(*else_e, Expr::If { .. }));
5972            }
5973            _ => panic!("expected if-else-if chain"),
5974        }
5975    }
5976
5977    #[test]
5978    fn expr_match() {
5979        let src = "match val {\n  0 => zero\n  n => other\n}";
5980        let (e, diags) = parse_expr_str(src);
5981        assert!(!diags.has_errors(), "{diags:?}");
5982        match e {
5983            Expr::Match { arms, .. } => assert_eq!(arms.len(), 2),
5984            _ => panic!("expected Match"),
5985        }
5986    }
5987
5988    #[test]
5989    fn expr_match_with_guard() {
5990        let src = "match x {\n  n if (n > 0) => pos\n  _ => other\n}";
5991        let (e, diags) = parse_expr_str(src);
5992        assert!(!diags.has_errors(), "{diags:?}");
5993        match e {
5994            Expr::Match { arms, .. } => {
5995                assert!(arms[0].guard.is_some());
5996                assert!(arms[1].guard.is_none());
5997            }
5998            _ => panic!("expected Match"),
5999        }
6000    }
6001
6002    #[test]
6003    fn expr_lambda_single_param() {
6004        let (e, diags) = parse_expr_str("(x) => x * 2");
6005        assert!(!diags.has_errors(), "{diags:?}");
6006        match e {
6007            Expr::Lambda { params, body, .. } => {
6008                assert_eq!(params.len(), 1);
6009                assert!(matches!(*body, Expr::Binary { op: BinOp::Mul, .. }));
6010            }
6011            _ => panic!("expected Lambda, got {e:?}"),
6012        }
6013    }
6014
6015    #[test]
6016    fn expr_lambda_no_params() {
6017        let (e, diags) = parse_expr_str("() => 42");
6018        assert!(!diags.has_errors(), "{diags:?}");
6019        match e {
6020            Expr::Lambda { params, .. } => assert_eq!(params.len(), 0),
6021            _ => panic!("expected Lambda"),
6022        }
6023    }
6024
6025    #[test]
6026    fn expr_lambda_block_body() {
6027        let (e, diags) = parse_expr_str("(a, b) => { a + b }");
6028        assert!(!diags.has_errors(), "{diags:?}");
6029        match e {
6030            Expr::Lambda { params, body, .. } => {
6031                assert_eq!(params.len(), 2);
6032                assert!(matches!(*body, Expr::Block { .. }));
6033            }
6034            _ => panic!("expected Lambda"),
6035        }
6036    }
6037
6038    #[test]
6039    fn expr_lambda_typed_params() {
6040        let (e, diags) = parse_expr_str("(x: Int) => x");
6041        assert!(!diags.has_errors(), "{diags:?}");
6042        match e {
6043            Expr::Lambda { params, .. } => {
6044                assert_eq!(params.len(), 1);
6045                assert!(params[0].ty.is_some());
6046            }
6047            _ => panic!("expected Lambda"),
6048        }
6049    }
6050
6051    #[test]
6052    fn expr_return() {
6053        let (e, diags) = parse_expr_str("return 42");
6054        assert!(!diags.has_errors(), "{diags:?}");
6055        assert!(matches!(e, Expr::Return { value: Some(_), .. }));
6056    }
6057
6058    #[test]
6059    fn expr_return_void() {
6060        // `return` inside a block followed by `}`
6061        let src = "fn test() {\nreturn\n}";
6062        let (m, diags) = parse(src);
6063        assert!(!diags.has_errors(), "{diags:?}");
6064        let fn_decl = match m.items.first().unwrap() {
6065            Item::Fn(f) => f,
6066            _ => panic!(),
6067        };
6068        // `return` is parsed as a tail or statement
6069        let body = fn_decl.body.as_ref().expect("expected function body");
6070        let has_return = body
6071            .tail
6072            .as_ref()
6073            .map(|t| matches!(**t, Expr::Return { .. }))
6074            .or_else(|| {
6075                body.stmts
6076                    .last()
6077                    .map(|s| matches!(s, Stmt::Expr(Expr::Return { .. })))
6078            })
6079            .unwrap_or(false);
6080        assert!(has_return, "expected return expr in body");
6081    }
6082
6083    #[test]
6084    fn expr_await() {
6085        let (e, diags) = parse_expr_str("await foo()");
6086        assert!(!diags.has_errors(), "{diags:?}");
6087        assert!(matches!(e, Expr::Await { .. }));
6088    }
6089
6090    #[test]
6091    fn expr_await_postfix() {
6092        let (e, diags) = parse_expr_str("foo().await");
6093        assert!(!diags.has_errors(), "{diags:?}");
6094        assert!(matches!(e, Expr::Await { .. }));
6095    }
6096
6097    #[test]
6098    fn expr_unreachable() {
6099        let (e, diags) = parse_expr_str("unreachable");
6100        assert!(!diags.has_errors(), "{diags:?}");
6101        assert!(matches!(e, Expr::Unreachable { .. }));
6102    }
6103
6104    #[test]
6105    fn expr_is_simple_type() {
6106        let (e, diags) = parse_expr_str("value is Int");
6107        assert!(!diags.has_errors(), "{diags:?}");
6108        match &e {
6109            Expr::Is { type_expr, .. } => {
6110                if let TypeExpr::Named { path, args, .. } = type_expr {
6111                    assert_eq!(path.segments[0].name, "Int");
6112                    assert!(args.is_empty());
6113                } else {
6114                    panic!("expected Named type_expr, got {type_expr:?}");
6115                }
6116            }
6117            _ => panic!("expected Expr::Is, got {e:?}"),
6118        }
6119    }
6120
6121    #[test]
6122    fn expr_is_generic_args_preserved() {
6123        let (e, diags) = parse_expr_str("x is Option[Int]");
6124        assert!(!diags.has_errors(), "{diags:?}");
6125        match &e {
6126            Expr::Is { type_expr, .. } => {
6127                if let TypeExpr::Named { path, args, .. } = type_expr {
6128                    assert_eq!(path.segments[0].name, "Option");
6129                    assert_eq!(args.len(), 1);
6130                } else {
6131                    panic!("expected Named type_expr, got {type_expr:?}");
6132                }
6133            }
6134            _ => panic!("expected Expr::Is, got {e:?}"),
6135        }
6136    }
6137
6138    #[test]
6139    fn expr_is_multi_arg_generic() {
6140        let (e, diags) = parse_expr_str("x is Result[String, Error]");
6141        assert!(!diags.has_errors(), "{diags:?}");
6142        match &e {
6143            Expr::Is { type_expr, .. } => {
6144                if let TypeExpr::Named { path, args, .. } = type_expr {
6145                    assert_eq!(path.segments[0].name, "Result");
6146                    assert_eq!(args.len(), 2);
6147                } else {
6148                    panic!("expected Named type_expr, got {type_expr:?}");
6149                }
6150            }
6151            _ => panic!("expected Expr::Is, got {e:?}"),
6152        }
6153    }
6154
6155    #[test]
6156    fn expr_is_nested_generic() {
6157        let (e, diags) = parse_expr_str("x is List[List[Int]]");
6158        assert!(!diags.has_errors(), "{diags:?}");
6159        match &e {
6160            Expr::Is { type_expr, .. } => {
6161                if let TypeExpr::Named { path, args, .. } = type_expr {
6162                    assert_eq!(path.segments[0].name, "List");
6163                    assert_eq!(args.len(), 1);
6164                    // Check nested generic
6165                    if let TypeExpr::Named {
6166                        path: inner,
6167                        args: inner_args,
6168                        ..
6169                    } = &args[0]
6170                    {
6171                        assert_eq!(inner.segments[0].name, "List");
6172                        assert_eq!(inner_args.len(), 1);
6173                    } else {
6174                        panic!("expected nested Named type_expr");
6175                    }
6176                } else {
6177                    panic!("expected Named type_expr, got {type_expr:?}");
6178                }
6179            }
6180            _ => panic!("expected Expr::Is, got {e:?}"),
6181        }
6182    }
6183
6184    #[test]
6185    fn expr_record_construct() {
6186        let (e, diags) = parse_expr_str("User { id: 1, name }");
6187        assert!(!diags.has_errors(), "{diags:?}");
6188        match e {
6189            Expr::RecordConstruct {
6190                path,
6191                fields,
6192                spread,
6193                ..
6194            } => {
6195                assert_eq!(path.segments[0].name, "User");
6196                assert_eq!(fields.len(), 2);
6197                assert_eq!(fields[0].name.name, "id");
6198                assert!(fields[0].value.is_some()); // `id: 1`
6199                assert_eq!(fields[1].name.name, "name");
6200                assert!(fields[1].value.is_none()); // shorthand
6201                assert!(spread.is_none());
6202            }
6203            _ => panic!("expected RecordConstruct, got {e:?}"),
6204        }
6205    }
6206
6207    #[test]
6208    fn expr_record_construct_with_spread() {
6209        let (e, diags) = parse_expr_str("User { name, ..defaults }");
6210        assert!(!diags.has_errors(), "{diags:?}");
6211        match e {
6212            Expr::RecordConstruct { spread, .. } => {
6213                assert!(spread.is_some());
6214            }
6215            _ => panic!("expected RecordConstruct"),
6216        }
6217    }
6218
6219    #[test]
6220    fn expr_let_statement_in_block() {
6221        let src = "fn test() {\nlet x = 42\nx\n}";
6222        let (m, diags) = parse(src);
6223        assert!(!diags.has_errors(), "{diags:?}");
6224        let fn_decl = match m.items.first().unwrap() {
6225            Item::Fn(f) => f,
6226            _ => panic!(),
6227        };
6228        let body = fn_decl.body.as_ref().expect("expected function body");
6229        assert_eq!(body.stmts.len(), 1);
6230        assert!(matches!(body.stmts[0], Stmt::Let(_)));
6231        assert!(body.tail.is_some());
6232    }
6233
6234    #[test]
6235    fn expr_let_mut_in_block() {
6236        let src = "fn test() {\nlet mut x = 0\nx\n}";
6237        let (m, diags) = parse(src);
6238        assert!(!diags.has_errors(), "{diags:?}");
6239        let fn_decl = match m.items.first().unwrap() {
6240            Item::Fn(f) => f,
6241            _ => panic!(),
6242        };
6243        let body = fn_decl.body.as_ref().expect("expected function body");
6244        assert!(
6245            matches!(&body.stmts[0], Stmt::Let(l) if matches!(l.pattern, Pattern::MutBind { .. }))
6246        );
6247    }
6248
6249    #[test]
6250    fn expr_for_loop_in_block() {
6251        let src = "fn test() {\nfor x in items { foo(x) }\n}";
6252        let (m, diags) = parse(src);
6253        assert!(!diags.has_errors(), "{diags:?}");
6254        let fn_decl = match m.items.first().unwrap() {
6255            Item::Fn(f) => f,
6256            _ => panic!(),
6257        };
6258        assert!(matches!(
6259            &fn_decl.body.as_ref().unwrap().stmts[0],
6260            Stmt::For(_)
6261        ));
6262    }
6263
6264    #[test]
6265    fn expr_while_loop_in_block() {
6266        let src = "fn test() {\nwhile (x > 0) { x -= 1 }\n}";
6267        let (m, diags) = parse(src);
6268        assert!(!diags.has_errors(), "{diags:?}");
6269        let fn_decl = match m.items.first().unwrap() {
6270            Item::Fn(f) => f,
6271            _ => panic!(),
6272        };
6273        assert!(matches!(
6274            &fn_decl.body.as_ref().unwrap().stmts[0],
6275            Stmt::While(_)
6276        ));
6277    }
6278
6279    #[test]
6280    fn expr_loop_in_block() {
6281        let src = "fn test() {\nloop { break }\n}";
6282        let (m, diags) = parse(src);
6283        assert!(!diags.has_errors(), "{diags:?}");
6284        let fn_decl = match m.items.first().unwrap() {
6285            Item::Fn(f) => f,
6286            _ => panic!(),
6287        };
6288        // Loop as last item in block becomes the tail expression
6289        assert!(matches!(
6290            fn_decl.body.as_ref().unwrap().tail.as_deref(),
6291            Some(Expr::Loop { .. })
6292        ));
6293    }
6294
6295    #[test]
6296    fn expr_complex_expression() {
6297        // `data |> parse |> validate`
6298        let (e, diags) = parse_expr_str("data |> parse |> validate");
6299        assert!(!diags.has_errors(), "{diags:?}");
6300        // Left-assoc: (data |> parse) |> validate
6301        match e {
6302            Expr::Pipe { left, .. } => {
6303                assert!(matches!(*left, Expr::Pipe { .. }));
6304            }
6305            _ => panic!("expected Pipe chain"),
6306        }
6307    }
6308
6309    #[test]
6310    fn expr_nested_binary() {
6311        // `(a + b) * c`
6312        let (e, diags) = parse_expr_str("(a + b) * c");
6313        assert!(!diags.has_errors(), "{diags:?}");
6314        match e {
6315            Expr::Binary {
6316                op: BinOp::Mul,
6317                left,
6318                ..
6319            } => {
6320                assert!(matches!(*left, Expr::Binary { op: BinOp::Add, .. }));
6321            }
6322            _ => panic!("expected Mul expr"),
6323        }
6324    }
6325
6326    #[test]
6327    fn expr_pattern_constructor_in_match() {
6328        let src = "match opt {\n  Some(x) => x\n  None => 0\n}";
6329        let (e, diags) = parse_expr_str(src);
6330        assert!(!diags.has_errors(), "{diags:?}");
6331        match e {
6332            Expr::Match { arms, .. } => {
6333                assert!(matches!(&arms[0].pattern, Pattern::Constructor { .. }));
6334                assert!(matches!(&arms[1].pattern, Pattern::Constructor { .. }));
6335            }
6336            _ => panic!("expected Match"),
6337        }
6338    }
6339
6340    #[test]
6341    fn expr_pattern_wildcard_in_match() {
6342        let src = "match x {\n  _ => 0\n}";
6343        let (e, diags) = parse_expr_str(src);
6344        assert!(!diags.has_errors(), "{diags:?}");
6345        match e {
6346            Expr::Match { arms, .. } => {
6347                assert!(matches!(&arms[0].pattern, Pattern::Wildcard { .. }));
6348            }
6349            _ => panic!("expected Match"),
6350        }
6351    }
6352
6353    #[test]
6354    fn expr_pattern_or() {
6355        let src = "match x {\n  1 | 2 => few\n  _ => many\n}";
6356        let (e, diags) = parse_expr_str(src);
6357        assert!(!diags.has_errors(), "{diags:?}");
6358        match e {
6359            Expr::Match { arms, .. } => {
6360                assert!(matches!(&arms[0].pattern, Pattern::Or { .. }));
6361            }
6362            _ => panic!("expected Match"),
6363        }
6364    }
6365
6366    #[test]
6367    fn expr_if_as_primary_expression() {
6368        // `let x = if (cond) { a } else { b }`
6369        let src = "fn test() {\nlet x = if (cond) { a } else { b }\nx\n}";
6370        let (m, diags) = parse(src);
6371        assert!(!diags.has_errors(), "{diags:?}");
6372        let fn_decl = match m.items.first().unwrap() {
6373            Item::Fn(f) => f,
6374            _ => panic!(),
6375        };
6376        // First stmt should be let with if-expr as value
6377        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6378            Stmt::Let(l) => {
6379                assert!(
6380                    matches!(l.value, Expr::If { .. }),
6381                    "expected If as let value"
6382                );
6383            }
6384            _ => panic!("expected Let stmt"),
6385        }
6386    }
6387
6388    #[test]
6389    fn expr_match_as_primary_expression() {
6390        // `let x = match val { 1 => a  _ => b }` (in fn body)
6391        let src = "fn test() {\nlet x = match val {\n1 => a\n_ => b\n}\nx\n}";
6392        let (m, diags) = parse(src);
6393        assert!(!diags.has_errors(), "{diags:?}");
6394        let fn_decl = match m.items.first().unwrap() {
6395            Item::Fn(f) => f,
6396            _ => panic!(),
6397        };
6398        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6399            Stmt::Let(l) => {
6400                assert!(
6401                    matches!(l.value, Expr::Match { .. }),
6402                    "expected Match as let value"
6403                );
6404            }
6405            _ => panic!("expected Let stmt"),
6406        }
6407    }
6408
6409    #[test]
6410    fn expr_loop_as_primary_expression() {
6411        // `let x = loop { break 42 }`
6412        let src = "fn test() {\nlet x = loop {\nbreak 42\n}\nx\n}";
6413        let (m, diags) = parse(src);
6414        assert!(!diags.has_errors(), "{diags:?}");
6415        let fn_decl = match m.items.first().unwrap() {
6416            Item::Fn(f) => f,
6417            _ => panic!(),
6418        };
6419        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6420            Stmt::Let(l) => {
6421                assert!(
6422                    matches!(l.value, Expr::Loop { .. }),
6423                    "expected Loop as let value"
6424                );
6425            }
6426            _ => panic!("expected Let stmt"),
6427        }
6428    }
6429
6430    #[test]
6431    fn expr_loop_as_function_arg() {
6432        let src = "fn test() {\nfoo(loop { break 1 })\n}";
6433        let (m, diags) = parse(src);
6434        assert!(!diags.has_errors(), "{diags:?}");
6435        let fn_decl = match m.items.first().unwrap() {
6436            Item::Fn(f) => f,
6437            _ => panic!(),
6438        };
6439        // The loop-as-arg should be the tail expression (a Call whose arg is Loop)
6440        let tail = fn_decl
6441            .body
6442            .as_ref()
6443            .unwrap()
6444            .tail
6445            .as_ref()
6446            .expect("expected tail");
6447        match tail.as_ref() {
6448            Expr::Call { args, .. } => {
6449                assert!(
6450                    matches!(args[0].value, Expr::Loop { .. }),
6451                    "expected Loop as arg"
6452                );
6453            }
6454            _ => panic!("expected Call expr"),
6455        }
6456    }
6457
6458    #[test]
6459    fn stmt_let_with_type_annotation() {
6460        let src = "fn test() {\nlet x: Int = 42\nx\n}";
6461        let (m, diags) = parse(src);
6462        assert!(!diags.has_errors(), "{diags:?}");
6463        let fn_decl = match m.items.first().unwrap() {
6464            Item::Fn(f) => f,
6465            _ => panic!(),
6466        };
6467        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6468            Stmt::Let(l) => {
6469                assert!(l.ty.is_some(), "expected type annotation on let");
6470            }
6471            _ => panic!("expected Let stmt"),
6472        }
6473    }
6474
6475    #[test]
6476    fn stmt_let_with_tuple_destructuring() {
6477        let src = "fn test() {\nlet (x, y) = get_point()\nx\n}";
6478        let (m, diags) = parse(src);
6479        assert!(!diags.has_errors(), "{diags:?}");
6480        let fn_decl = match m.items.first().unwrap() {
6481            Item::Fn(f) => f,
6482            _ => panic!(),
6483        };
6484        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6485            Stmt::Let(l) => {
6486                assert!(
6487                    matches!(l.pattern, Pattern::Tuple { .. }),
6488                    "expected tuple pattern, got {:?}",
6489                    l.pattern
6490                );
6491            }
6492            _ => panic!("expected Let stmt"),
6493        }
6494    }
6495
6496    #[test]
6497    fn stmt_guard_in_block() {
6498        let src = "fn test() {\nguard (x > 0) else { return }\nx\n}";
6499        let (m, diags) = parse(src);
6500        assert!(!diags.has_errors(), "{diags:?}");
6501        let fn_decl = match m.items.first().unwrap() {
6502            Item::Fn(f) => f,
6503            _ => panic!(),
6504        };
6505        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6506            Stmt::Guard(g) => {
6507                assert!(
6508                    !g.else_block.stmts.is_empty()
6509                        || g.else_block.tail.is_some()
6510                        || !g.else_block.stmts.is_empty(),
6511                    "expected non-empty else block"
6512                );
6513            }
6514            _ => panic!(
6515                "expected Guard stmt, got {:?}",
6516                fn_decl.body.as_ref().unwrap().stmts[0]
6517            ),
6518        }
6519    }
6520
6521    #[test]
6522    fn stmt_handling_block_with_multiple_bindings() {
6523        let src = "fn test() {\nhandling (Log with logger, Clock with mock) {\ndo_work()\n}\n}";
6524        let (m, diags) = parse(src);
6525        assert!(!diags.has_errors(), "{diags:?}");
6526        let fn_decl = match m.items.first().unwrap() {
6527            Item::Fn(f) => f,
6528            _ => panic!(),
6529        };
6530        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6531            Stmt::Handling(h) => {
6532                assert_eq!(h.handlers.len(), 2, "expected 2 handler bindings");
6533            }
6534            _ => panic!(
6535                "expected Handling stmt, got {:?}",
6536                fn_decl.body.as_ref().unwrap().stmts[0]
6537            ),
6538        }
6539    }
6540
6541    #[test]
6542    fn stmt_handling_block_single_binding() {
6543        let src = "fn test() {\nhandling (Log with handler) {\ndo_work()\n}\n}";
6544        let (m, diags) = parse(src);
6545        assert!(!diags.has_errors(), "{diags:?}");
6546        let fn_decl = match m.items.first().unwrap() {
6547            Item::Fn(f) => f,
6548            _ => panic!(),
6549        };
6550        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6551            Stmt::Handling(h) => {
6552                assert_eq!(h.handlers.len(), 1);
6553            }
6554            _ => panic!("expected Handling stmt"),
6555        }
6556    }
6557
6558    #[test]
6559    fn stmt_continuation_operator_at_end_of_line() {
6560        // Operator at end of line: `a +\n  b` should parse as `a + b`
6561        let src = "fn test() {\nlet x = a +\n  b\nx\n}";
6562        let (m, diags) = parse(src);
6563        assert!(!diags.has_errors(), "{diags:?}");
6564        let fn_decl = match m.items.first().unwrap() {
6565            Item::Fn(f) => f,
6566            _ => panic!(),
6567        };
6568        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6569            Stmt::Let(l) => {
6570                assert!(
6571                    matches!(l.value, Expr::Binary { op: BinOp::Add, .. }),
6572                    "expected binary Add across lines"
6573                );
6574            }
6575            _ => panic!("expected Let stmt"),
6576        }
6577    }
6578
6579    #[test]
6580    fn stmt_continuation_pipe_at_start_of_next_line() {
6581        // `|>` at start of next line continues the expression
6582        let src = "fn test() {\nlet x = data\n  |> transform\nx\n}";
6583        let (m, diags) = parse(src);
6584        assert!(!diags.has_errors(), "{diags:?}");
6585        let fn_decl = match m.items.first().unwrap() {
6586            Item::Fn(f) => f,
6587            _ => panic!(),
6588        };
6589        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6590            Stmt::Let(l) => {
6591                assert!(
6592                    matches!(l.value, Expr::Pipe { .. }),
6593                    "expected Pipe across lines"
6594                );
6595            }
6596            _ => panic!("expected Let stmt"),
6597        }
6598    }
6599
6600    #[test]
6601    fn stmt_continuation_dot_at_start_of_next_line() {
6602        // `.method()` at start of next line continues the expression
6603        let src = "fn test() {\nlet x = obj\n  .field\nx\n}";
6604        let (m, diags) = parse(src);
6605        assert!(!diags.has_errors(), "{diags:?}");
6606        let fn_decl = match m.items.first().unwrap() {
6607            Item::Fn(f) => f,
6608            _ => panic!(),
6609        };
6610        match &fn_decl.body.as_ref().unwrap().stmts[0] {
6611            Stmt::Let(l) => {
6612                assert!(
6613                    matches!(l.value, Expr::FieldAccess { .. }),
6614                    "expected FieldAccess across lines"
6615                );
6616            }
6617            _ => panic!("expected Let stmt"),
6618        }
6619    }
6620
6621    #[test]
6622    fn stmt_continuation_else_on_next_line() {
6623        // `else` on a new line after `}` continues the if-expression (spec §3.2 rule 8)
6624        let src = "fn test() -> Int {\nif (true) { 1 }\nelse { 2 }\n}";
6625        let (m, diags) = parse(src);
6626        assert!(!diags.has_errors(), "{diags:?}");
6627        let fn_decl = match m.items.first().unwrap() {
6628            Item::Fn(f) => f,
6629            _ => panic!(),
6630        };
6631        // The if-else is the block's tail expression
6632        let tail = fn_decl
6633            .body
6634            .as_ref()
6635            .unwrap()
6636            .tail
6637            .as_ref()
6638            .expect("expected tail expr");
6639        match tail.as_ref() {
6640            Expr::If { else_block, .. } => {
6641                assert!(else_block.is_some(), "expected else block across lines");
6642            }
6643            other => panic!("expected If expr, got {other:?}"),
6644        }
6645    }
6646
6647    #[test]
6648    fn stmt_continuation_else_if_on_next_line() {
6649        // `else if` chain across lines
6650        let src = "fn test() -> Int {\nif (true) { 1 }\nelse if (false) { 2 }\nelse { 3 }\n}";
6651        let (m, diags) = parse(src);
6652        assert!(!diags.has_errors(), "{diags:?}");
6653        let fn_decl = match m.items.first().unwrap() {
6654            Item::Fn(f) => f,
6655            _ => panic!(),
6656        };
6657        let tail = fn_decl
6658            .body
6659            .as_ref()
6660            .unwrap()
6661            .tail
6662            .as_ref()
6663            .expect("expected tail expr");
6664        match tail.as_ref() {
6665            Expr::If { else_block, .. } => {
6666                let inner = else_block.as_ref().expect("expected else-if chain");
6667                assert!(
6668                    matches!(inner.as_ref(), Expr::If { .. }),
6669                    "expected nested If in else-if chain"
6670                );
6671            }
6672            other => panic!("expected If expr, got {other:?}"),
6673        }
6674    }
6675
6676    #[test]
6677    fn stmt_semicolon_separates_statements() {
6678        // Semicolons always terminate, even on same line
6679        let src = "fn test() {\nlet x = 1; let y = 2\nx\n}";
6680        let (m, diags) = parse(src);
6681        assert!(!diags.has_errors(), "{diags:?}");
6682        let fn_decl = match m.items.first().unwrap() {
6683            Item::Fn(f) => f,
6684            _ => panic!(),
6685        };
6686        assert_eq!(
6687            fn_decl.body.as_ref().unwrap().stmts.len(),
6688            2,
6689            "expected 2 stmts from semicolon-separated line"
6690        );
6691    }
6692
6693    #[test]
6694    fn stmt_loop_break_with_value() {
6695        // `loop { break value }` — break can carry a value
6696        let src = "fn test() {\nloop { break 42 }\n}";
6697        let (m, diags) = parse(src);
6698        assert!(!diags.has_errors(), "{diags:?}");
6699        let fn_decl = match m.items.first().unwrap() {
6700            Item::Fn(f) => f,
6701            _ => panic!(),
6702        };
6703        // Loop as last item becomes the tail expression
6704        match fn_decl.body.as_ref().unwrap().tail.as_deref() {
6705            Some(Expr::Loop { body, .. }) => {
6706                // The loop body should contain a Break with a value
6707                let has_break = body
6708                    .stmts
6709                    .iter()
6710                    .any(|s| matches!(s, Stmt::Expr(Expr::Break { value: Some(_), .. })))
6711                    || body
6712                        .tail
6713                        .as_ref()
6714                        .is_some_and(|t| matches!(**t, Expr::Break { value: Some(_), .. }));
6715                assert!(has_break, "expected break with value in loop body");
6716            }
6717            _ => panic!("expected Loop tail expression"),
6718        }
6719    }
6720
6721    // ── P2.7: Pattern parsing tests ──────────────────────────────────────────
6722
6723    fn parse_pat(src: &str) -> (Pattern, DiagnosticBag) {
6724        // Wrap in a match expression to trigger pattern parsing.
6725        let wrapped = format!("fn f() {{ match x {{\n{src}\n}} }}");
6726        let (m, diags) = parse(&wrapped);
6727        let fn_decl = match m.items.first().unwrap() {
6728            Item::Fn(f) => f,
6729            _ => panic!("expected fn"),
6730        };
6731        let mat = match fn_decl.body.as_ref().unwrap().tail.as_deref() {
6732            Some(Expr::Match { arms, .. }) => arms.clone(),
6733            _ => match fn_decl.body.as_ref().unwrap().stmts.first() {
6734                Some(Stmt::Expr(Expr::Match { arms, .. })) => arms.clone(),
6735                _ => panic!("expected match expression"),
6736            },
6737        };
6738        let pat = mat
6739            .into_iter()
6740            .next()
6741            .expect("expected at least one arm")
6742            .pattern;
6743        (pat, diags)
6744    }
6745
6746    #[test]
6747    fn pattern_wildcard() {
6748        let (pat, diags) = parse_pat("_ => 1");
6749        assert!(!diags.has_errors(), "{diags:?}");
6750        assert!(matches!(pat, Pattern::Wildcard { .. }));
6751    }
6752
6753    #[test]
6754    fn pattern_bind() {
6755        let (pat, diags) = parse_pat("name => 1");
6756        assert!(!diags.has_errors(), "{diags:?}");
6757        assert!(matches!(pat, Pattern::Bind { .. }));
6758        if let Pattern::Bind { name, .. } = pat {
6759            assert_eq!(name.name, "name");
6760        }
6761    }
6762
6763    #[test]
6764    fn pattern_mut_bind() {
6765        let (pat, diags) = parse_pat("mut x => 1");
6766        assert!(!diags.has_errors(), "{diags:?}");
6767        assert!(matches!(pat, Pattern::MutBind { .. }));
6768        if let Pattern::MutBind { name, .. } = pat {
6769            assert_eq!(name.name, "x");
6770        }
6771    }
6772
6773    #[test]
6774    fn pattern_literal_int() {
6775        let (pat, diags) = parse_pat("42 => 1");
6776        assert!(!diags.has_errors(), "{diags:?}");
6777        assert!(matches!(
6778            pat,
6779            Pattern::Literal {
6780                lit: Literal::Int(_),
6781                ..
6782            }
6783        ));
6784    }
6785
6786    #[test]
6787    fn pattern_literal_string() {
6788        let (pat, diags) = parse_pat(r#""hello" => 1"#);
6789        assert!(!diags.has_errors(), "{diags:?}");
6790        assert!(matches!(
6791            pat,
6792            Pattern::Literal {
6793                lit: Literal::String(_),
6794                ..
6795            }
6796        ));
6797    }
6798
6799    #[test]
6800    fn pattern_literal_bool_true() {
6801        let (pat, diags) = parse_pat("true => 1");
6802        assert!(!diags.has_errors(), "{diags:?}");
6803        assert!(matches!(
6804            pat,
6805            Pattern::Literal {
6806                lit: Literal::Bool(true),
6807                ..
6808            }
6809        ));
6810    }
6811
6812    #[test]
6813    fn pattern_literal_bool_false() {
6814        let (pat, diags) = parse_pat("false => 1");
6815        assert!(!diags.has_errors(), "{diags:?}");
6816        assert!(matches!(
6817            pat,
6818            Pattern::Literal {
6819                lit: Literal::Bool(false),
6820                ..
6821            }
6822        ));
6823    }
6824
6825    #[test]
6826    fn pattern_constructor_some() {
6827        let (pat, diags) = parse_pat("Some(x) => 1");
6828        assert!(!diags.has_errors(), "{diags:?}");
6829        if let Pattern::Constructor { fields, .. } = pat {
6830            assert_eq!(fields.len(), 1);
6831            assert!(matches!(fields[0], Pattern::Bind { .. }));
6832        } else {
6833            panic!("expected Constructor pattern");
6834        }
6835    }
6836
6837    #[test]
6838    fn pattern_constructor_err() {
6839        let (pat, diags) = parse_pat("Err(e) => 1");
6840        assert!(!diags.has_errors(), "{diags:?}");
6841        assert!(matches!(pat, Pattern::Constructor { .. }));
6842    }
6843
6844    #[test]
6845    fn pattern_record_shorthand() {
6846        let (pat, diags) = parse_pat("Point { x, y } => 1");
6847        assert!(!diags.has_errors(), "{diags:?}");
6848        if let Pattern::Record { fields, rest, .. } = pat {
6849            assert_eq!(fields.len(), 2);
6850            assert_eq!(fields[0].name.name, "x");
6851            assert!(
6852                fields[0].pattern.is_none(),
6853                "shorthand should have no sub-pattern"
6854            );
6855            assert_eq!(fields[1].name.name, "y");
6856            assert!(!rest, "no rest expected");
6857        } else {
6858            panic!("expected Record pattern");
6859        }
6860    }
6861
6862    #[test]
6863    fn pattern_record_with_rename() {
6864        let (pat, diags) = parse_pat("User { name: n, age: a } => 1");
6865        assert!(!diags.has_errors(), "{diags:?}");
6866        if let Pattern::Record { fields, rest, .. } = pat {
6867            assert_eq!(fields.len(), 2);
6868            assert_eq!(fields[0].name.name, "name");
6869            assert!(fields[0].pattern.is_some());
6870            assert!(!rest);
6871        } else {
6872            panic!("expected Record pattern");
6873        }
6874    }
6875
6876    #[test]
6877    fn pattern_record_with_rest() {
6878        let (pat, diags) = parse_pat("User { name: n, .. } => 1");
6879        assert!(!diags.has_errors(), "{diags:?}");
6880        if let Pattern::Record { fields, rest, .. } = pat {
6881            assert_eq!(fields.len(), 1);
6882            assert_eq!(fields[0].name.name, "name");
6883            assert!(rest, "rest flag should be set for `..`");
6884        } else {
6885            panic!("expected Record pattern");
6886        }
6887    }
6888
6889    #[test]
6890    fn pattern_tuple() {
6891        let (pat, diags) = parse_pat("(a, b, c) => 1");
6892        assert!(!diags.has_errors(), "{diags:?}");
6893        if let Pattern::Tuple { elems, .. } = pat {
6894            assert_eq!(elems.len(), 3);
6895        } else {
6896            panic!("expected Tuple pattern");
6897        }
6898    }
6899
6900    #[test]
6901    fn pattern_list_with_rest() {
6902        let (pat, diags) = parse_pat("[first, ..rest] => 1");
6903        assert!(!diags.has_errors(), "{diags:?}");
6904        if let Pattern::List { elems, rest, .. } = pat {
6905            assert_eq!(elems.len(), 1);
6906            assert!(matches!(elems[0], Pattern::Bind { .. }));
6907            let r = rest.expect("rest pattern expected");
6908            assert!(matches!(*r, Pattern::Bind { .. }));
6909        } else {
6910            panic!("expected List pattern");
6911        }
6912    }
6913
6914    #[test]
6915    fn pattern_list_rest_only() {
6916        let (pat, diags) = parse_pat("[..] => 1");
6917        assert!(!diags.has_errors(), "{diags:?}");
6918        if let Pattern::List { elems, rest, .. } = pat {
6919            assert!(elems.is_empty());
6920            assert!(rest.is_some());
6921        } else {
6922            panic!("expected List pattern");
6923        }
6924    }
6925
6926    #[test]
6927    fn pattern_or() {
6928        let (pat, diags) = parse_pat("A | B | C => 1");
6929        assert!(!diags.has_errors(), "{diags:?}");
6930        if let Pattern::Or { alternatives, .. } = pat {
6931            assert_eq!(alternatives.len(), 3);
6932        } else {
6933            panic!("expected Or pattern");
6934        }
6935    }
6936
6937    #[test]
6938    fn pattern_range() {
6939        let (pat, diags) = parse_pat("1..10 => 1");
6940        assert!(!diags.has_errors(), "{diags:?}");
6941        if let Pattern::Range { inclusive, .. } = pat {
6942            assert!(!inclusive);
6943        } else {
6944            panic!("expected Range pattern");
6945        }
6946    }
6947
6948    #[test]
6949    fn pattern_nested_constructor() {
6950        // Some(Ok((a, b)))
6951        let (pat, diags) = parse_pat("Some(Ok((a, b))) => 1");
6952        assert!(!diags.has_errors(), "{diags:?}");
6953        if let Pattern::Constructor { fields, .. } = pat {
6954            assert_eq!(fields.len(), 1, "Some should have 1 field");
6955            if let Pattern::Constructor { fields: inner, .. } = &fields[0] {
6956                assert_eq!(inner.len(), 1, "Ok should have 1 field");
6957                assert!(matches!(inner[0], Pattern::Tuple { .. }));
6958            } else {
6959                panic!("expected inner Constructor (Ok)");
6960            }
6961        } else {
6962            panic!("expected outer Constructor (Some)");
6963        }
6964    }
6965
6966    #[test]
6967    fn pattern_or_in_match_arm() {
6968        let src = "fn f() { match x {\n1 | 2 => \"small\"\n_ => \"other\"\n} }";
6969        let (m, diags) = parse(src);
6970        assert!(!diags.has_errors(), "{diags:?}");
6971        let fn_decl = match m.items.first().unwrap() {
6972            Item::Fn(f) => f,
6973            _ => panic!(),
6974        };
6975        let arms = match fn_decl.body.as_ref().unwrap().tail.as_deref() {
6976            Some(Expr::Match { arms, .. }) => arms.clone(),
6977            _ => match fn_decl.body.as_ref().unwrap().stmts.first() {
6978                Some(Stmt::Expr(Expr::Match { arms, .. })) => arms.clone(),
6979                _ => panic!("expected match"),
6980            },
6981        };
6982        assert_eq!(arms.len(), 2);
6983        assert!(matches!(arms[0].pattern, Pattern::Or { .. }));
6984    }
6985
6986    #[test]
6987    fn pattern_guard_in_match_arm() {
6988        let src = "fn f() { match x {\nn if (n > 100) => \"large\"\n_ => \"other\"\n} }";
6989        let (m, diags) = parse(src);
6990        assert!(!diags.has_errors(), "{diags:?}");
6991        let fn_decl = match m.items.first().unwrap() {
6992            Item::Fn(f) => f,
6993            _ => panic!(),
6994        };
6995        let arms = match fn_decl.body.as_ref().unwrap().tail.as_deref() {
6996            Some(Expr::Match { arms, .. }) => arms.clone(),
6997            _ => match fn_decl.body.as_ref().unwrap().stmts.first() {
6998                Some(Stmt::Expr(Expr::Match { arms, .. })) => arms.clone(),
6999                _ => panic!("expected match"),
7000            },
7001        };
7002        assert_eq!(arms.len(), 2);
7003        assert!(arms[0].guard.is_some(), "first arm should have guard");
7004        assert!(arms[1].guard.is_none());
7005    }
7006
7007    #[test]
7008    fn pattern_full_match_example() {
7009        // Based on the spec example:
7010        // match value {
7011        //   0 => "zero"
7012        //   1 | 2 => "small"
7013        //   n if (n > 100) => "large"
7014        //   Point { x: 0, y } => "on y-axis"
7015        //   Some(Ok(v)) => "got it"
7016        //   [first, ..rest] => "head"
7017        //   _ => "other"
7018        // }
7019        let src = r#"fn f() {
7020match value {
7021  0 => "zero"
7022  1 | 2 => "small"
7023  n if (n > 100) => "large"
7024  Point { x: 0, y } => "on y-axis"
7025  Some(Ok(v)) => "got it"
7026  [first, ..rest] => "head"
7027  _ => "other"
7028}
7029}"#;
7030        let (m, diags) = parse(src);
7031        assert!(!diags.has_errors(), "{diags:?}");
7032        let fn_decl = match m.items.first().unwrap() {
7033            Item::Fn(f) => f,
7034            _ => panic!(),
7035        };
7036        let arms = match fn_decl.body.as_ref().unwrap().tail.as_deref() {
7037            Some(Expr::Match { arms, .. }) => arms.clone(),
7038            _ => match fn_decl.body.as_ref().unwrap().stmts.first() {
7039                Some(Stmt::Expr(Expr::Match { arms, .. })) => arms.clone(),
7040                _ => panic!("expected match"),
7041            },
7042        };
7043        assert_eq!(arms.len(), 7);
7044        assert!(matches!(
7045            arms[0].pattern,
7046            Pattern::Literal {
7047                lit: Literal::Int(_),
7048                ..
7049            }
7050        ));
7051        assert!(matches!(arms[1].pattern, Pattern::Or { .. }));
7052        assert!(arms[2].guard.is_some());
7053        assert!(matches!(arms[3].pattern, Pattern::Record { .. }));
7054        assert!(matches!(arms[4].pattern, Pattern::Constructor { .. }));
7055        assert!(matches!(arms[5].pattern, Pattern::List { .. }));
7056        assert!(matches!(arms[6].pattern, Pattern::Wildcard { .. }));
7057    }
7058
7059    #[test]
7060    fn pattern_if_let() {
7061        let src = "fn f() { if (let Some(user) = find(id)) { consume(user) } }";
7062        let (m, diags) = parse(src);
7063        assert!(!diags.has_errors(), "{diags:?}");
7064        let fn_decl = match m.items.first().unwrap() {
7065            Item::Fn(f) => f,
7066            _ => panic!(),
7067        };
7068        // The if-let should parse without errors — let_pattern should be Some.
7069        let has_if_let = fn_decl.body.as_ref().unwrap().stmts.iter().any(|s| {
7070            matches!(
7071                s,
7072                Stmt::Expr(Expr::If {
7073                    let_pattern: Some(_),
7074                    ..
7075                })
7076            )
7077        }) || fn_decl
7078            .body
7079            .as_ref()
7080            .unwrap()
7081            .tail
7082            .as_ref()
7083            .is_some_and(|t| {
7084                matches!(
7085                    t.as_ref(),
7086                    Expr::If {
7087                        let_pattern: Some(_),
7088                        ..
7089                    }
7090                )
7091            });
7092        assert!(has_if_let, "expected if-let expression with let_pattern");
7093    }
7094
7095    // ── P2.8: Type expression parsing tests ──────────────────────────────────
7096
7097    /// Parse a type expression from a function parameter annotation: `fn f(x: <ty>) {}`.
7098    fn parse_type_str(ty: &str) -> (TypeExpr, DiagnosticBag) {
7099        let src = format!("fn f(x: {ty}) {{}}\n");
7100        let (m, diags) = parse(&src);
7101        let Item::Fn(f) = &m.items[0] else {
7102            panic!("expected fn")
7103        };
7104        let ty = f.params[0].ty.clone().expect("param should have type");
7105        (ty, diags)
7106    }
7107
7108    #[test]
7109    fn type_named_simple() {
7110        let (ty, diags) = parse_type_str("Int");
7111        assert!(!diags.has_errors(), "{diags:?}");
7112        let TypeExpr::Named { path, args, .. } = ty else {
7113            panic!("expected Named")
7114        };
7115        assert_eq!(path.segments[0].name, "Int");
7116        assert!(args.is_empty());
7117    }
7118
7119    #[test]
7120    fn type_named_module_path() {
7121        let (ty, diags) = parse_type_str("app.models.User");
7122        assert!(!diags.has_errors(), "{diags:?}");
7123        let TypeExpr::Named { path, .. } = ty else {
7124            panic!("expected Named")
7125        };
7126        let names: Vec<&str> = path.segments.iter().map(|s| s.name.as_str()).collect();
7127        assert_eq!(names, ["app", "models", "User"]);
7128    }
7129
7130    #[test]
7131    fn type_generic_single() {
7132        let (ty, diags) = parse_type_str("List[Int]");
7133        assert!(!diags.has_errors(), "{diags:?}");
7134        let TypeExpr::Named { path, args, .. } = ty else {
7135            panic!("expected Named")
7136        };
7137        assert_eq!(path.segments[0].name, "List");
7138        assert_eq!(args.len(), 1);
7139        assert!(matches!(&args[0], TypeExpr::Named { path, .. } if path.segments[0].name == "Int"));
7140    }
7141
7142    #[test]
7143    fn type_generic_two_params() {
7144        let (ty, diags) = parse_type_str("Map[String, Int]");
7145        assert!(!diags.has_errors(), "{diags:?}");
7146        let TypeExpr::Named { path, args, .. } = ty else {
7147            panic!("expected Named")
7148        };
7149        assert_eq!(path.segments[0].name, "Map");
7150        assert_eq!(args.len(), 2);
7151    }
7152
7153    #[test]
7154    fn type_generic_nested() {
7155        let (ty, diags) = parse_type_str("Map[String, List[User]]");
7156        assert!(!diags.has_errors(), "{diags:?}");
7157        let TypeExpr::Named { args, .. } = ty else {
7158            panic!("expected Named")
7159        };
7160        assert_eq!(args.len(), 2);
7161        let TypeExpr::Named {
7162            path: inner_path,
7163            args: inner_args,
7164            ..
7165        } = &args[1]
7166        else {
7167            panic!("expected inner Named")
7168        };
7169        assert_eq!(inner_path.segments[0].name, "List");
7170        assert_eq!(inner_args.len(), 1);
7171    }
7172
7173    #[test]
7174    fn type_tuple_unit() {
7175        let (ty, diags) = parse_type_str("()");
7176        assert!(!diags.has_errors(), "{diags:?}");
7177        let TypeExpr::Tuple { elems, .. } = ty else {
7178            panic!("expected Tuple")
7179        };
7180        assert!(elems.is_empty());
7181    }
7182
7183    #[test]
7184    fn type_tuple_two_elems() {
7185        let (ty, diags) = parse_type_str("(Int, String)");
7186        assert!(!diags.has_errors(), "{diags:?}");
7187        let TypeExpr::Tuple { elems, .. } = ty else {
7188            panic!("expected Tuple")
7189        };
7190        assert_eq!(elems.len(), 2);
7191    }
7192
7193    #[test]
7194    fn type_tuple_three_elems() {
7195        let (ty, diags) = parse_type_str("(Int, String, Bool)");
7196        assert!(!diags.has_errors(), "{diags:?}");
7197        let TypeExpr::Tuple { elems, .. } = ty else {
7198            panic!("expected Tuple")
7199        };
7200        assert_eq!(elems.len(), 3);
7201    }
7202
7203    #[test]
7204    fn type_fn_no_params() {
7205        let (ty, diags) = parse_type_str("Fn() -> Void");
7206        assert!(!diags.has_errors(), "{diags:?}");
7207        let TypeExpr::Function {
7208            params,
7209            ret,
7210            effects,
7211            ..
7212        } = ty
7213        else {
7214            panic!("expected Function")
7215        };
7216        assert!(params.is_empty());
7217        assert!(effects.is_empty());
7218        assert!(
7219            matches!(ret.as_ref(), TypeExpr::Named { path, .. } if path.segments[0].name == "Void")
7220        );
7221    }
7222
7223    #[test]
7224    fn type_fn_with_params_and_return() {
7225        let (ty, diags) = parse_type_str("Fn(Int, Int) -> Int");
7226        assert!(!diags.has_errors(), "{diags:?}");
7227        let TypeExpr::Function {
7228            params,
7229            ret,
7230            effects,
7231            ..
7232        } = ty
7233        else {
7234            panic!("expected Function")
7235        };
7236        assert_eq!(params.len(), 2);
7237        assert!(effects.is_empty());
7238        assert!(
7239            matches!(ret.as_ref(), TypeExpr::Named { path, .. } if path.segments[0].name == "Int")
7240        );
7241    }
7242
7243    #[test]
7244    fn type_fn_with_effect_clause() {
7245        let (ty, diags) = parse_type_str("Fn(String) -> Void with Log");
7246        assert!(!diags.has_errors(), "{diags:?}");
7247        let TypeExpr::Function {
7248            params, effects, ..
7249        } = ty
7250        else {
7251            panic!("expected Function")
7252        };
7253        assert_eq!(params.len(), 1);
7254        assert_eq!(effects.len(), 1);
7255        assert_eq!(effects[0].segments[0].name, "Log");
7256    }
7257
7258    #[test]
7259    fn type_fn_with_multiple_effects() {
7260        let (ty, diags) = parse_type_str("Fn() -> Void with Log, Io");
7261        assert!(!diags.has_errors(), "{diags:?}");
7262        let TypeExpr::Function { effects, .. } = ty else {
7263            panic!("expected Function")
7264        };
7265        assert_eq!(effects.len(), 2);
7266        assert_eq!(effects[0].segments[0].name, "Log");
7267        assert_eq!(effects[1].segments[0].name, "Io");
7268    }
7269
7270    #[test]
7271    fn type_optional_shorthand() {
7272        let (ty, diags) = parse_type_str("User?");
7273        assert!(!diags.has_errors(), "{diags:?}");
7274        let TypeExpr::Optional { inner, .. } = ty else {
7275            panic!("expected Optional")
7276        };
7277        let TypeExpr::Named { path, .. } = inner.as_ref() else {
7278            panic!("expected Named inner")
7279        };
7280        assert_eq!(path.segments[0].name, "User");
7281    }
7282
7283    #[test]
7284    fn type_optional_generic() {
7285        let (ty, diags) = parse_type_str("List[Int]?");
7286        assert!(!diags.has_errors(), "{diags:?}");
7287        let TypeExpr::Optional { inner, .. } = ty else {
7288            panic!("expected Optional")
7289        };
7290        assert!(matches!(inner.as_ref(), TypeExpr::Named { .. }));
7291    }
7292
7293    #[test]
7294    fn type_self_in_impl() {
7295        // `Self` is a valid type expression; test via record field.
7296        let src = "record Wrap { inner: Self }\n";
7297        let (m, diags) = parse(src);
7298        assert!(!diags.has_errors(), "{diags:?}");
7299        let Item::Record(r) = &m.items[0] else {
7300            panic!()
7301        };
7302        assert!(matches!(r.fields[0].ty, TypeExpr::SelfType { .. }));
7303    }
7304
7305    #[test]
7306    fn type_deeply_nested_generics() {
7307        // Map[String, List[Result[User, String]]]
7308        let (ty, diags) = parse_type_str("Map[String, List[Result[User, String]]]");
7309        assert!(!diags.has_errors(), "{diags:?}");
7310        let TypeExpr::Named { path, args, .. } = ty else {
7311            panic!("expected Named")
7312        };
7313        assert_eq!(path.segments[0].name, "Map");
7314        assert_eq!(args.len(), 2);
7315        let TypeExpr::Named {
7316            path: list_path,
7317            args: list_args,
7318            ..
7319        } = &args[1]
7320        else {
7321            panic!("expected List")
7322        };
7323        assert_eq!(list_path.segments[0].name, "List");
7324        assert_eq!(list_args.len(), 1);
7325        let TypeExpr::Named {
7326            path: result_path,
7327            args: result_args,
7328            ..
7329        } = &list_args[0]
7330        else {
7331            panic!("expected Result")
7332        };
7333        assert_eq!(result_path.segments[0].name, "Result");
7334        assert_eq!(result_args.len(), 2);
7335    }
7336
7337    // ─── Effect declaration tests ─────────────────────────────────────────────
7338
7339    #[test]
7340    fn effect_empty_body() {
7341        let src = "effect Log {\n}\n";
7342        let (m, diags) = parse(src);
7343        assert!(!diags.has_errors(), "{diags:?}");
7344        assert_eq!(m.items.len(), 1);
7345        let Item::Effect(e) = &m.items[0] else {
7346            panic!("expected Effect")
7347        };
7348        assert_eq!(e.name.name, "Log");
7349        assert!(e.operations.is_empty());
7350    }
7351
7352    #[test]
7353    fn effect_with_operations() {
7354        let src = "effect Log {\n  fn log(level: Level, message: String) -> Void\n}\n";
7355        let (m, diags) = parse(src);
7356        assert!(!diags.has_errors(), "{diags:?}");
7357        let Item::Effect(e) = &m.items[0] else {
7358            panic!("expected Effect")
7359        };
7360        assert_eq!(e.name.name, "Log");
7361        assert_eq!(e.operations.len(), 1);
7362        assert_eq!(e.operations[0].name.name, "log");
7363        assert_eq!(e.operations[0].params.len(), 2);
7364    }
7365
7366    #[test]
7367    fn effect_multiple_operations() {
7368        let src = "effect Storage {\n  fn read(key: String) -> Option[Bytes]\n  fn write(key: String, val: Bytes) -> Void\n}\n";
7369        let (m, diags) = parse(src);
7370        assert!(!diags.has_errors(), "{diags:?}");
7371        let Item::Effect(e) = &m.items[0] else {
7372            panic!("expected Effect")
7373        };
7374        assert_eq!(e.operations.len(), 2);
7375        assert_eq!(e.operations[0].name.name, "read");
7376        assert_eq!(e.operations[1].name.name, "write");
7377    }
7378
7379    #[test]
7380    fn effect_composite() {
7381        let src = "effect Observable = Log + Trace + Metrics\n";
7382        let (m, diags) = parse(src);
7383        assert!(!diags.has_errors(), "{diags:?}");
7384        assert_eq!(m.items.len(), 1);
7385        let Item::Effect(e) = &m.items[0] else {
7386            panic!("expected Effect")
7387        };
7388        assert_eq!(e.name.name, "Observable");
7389        assert!(
7390            e.operations.is_empty(),
7391            "composite effects have no operations"
7392        );
7393    }
7394
7395    #[test]
7396    fn effect_with_visibility() {
7397        let src = "public effect Log {\n  fn log(msg: String) -> Void\n}\n";
7398        let (m, diags) = parse(src);
7399        assert!(!diags.has_errors(), "{diags:?}");
7400        let Item::Effect(e) = &m.items[0] else {
7401            panic!("expected Effect")
7402        };
7403        assert_eq!(e.visibility, Visibility::Public);
7404    }
7405
7406    // ─── Annotation tests ─────────────────────────────────────────────────────
7407
7408    #[test]
7409    fn annotation_no_args() {
7410        let src = "@deprecated\nfn old() {}\n";
7411        let (m, diags) = parse(src);
7412        assert!(!diags.has_errors(), "{diags:?}");
7413        let Item::Fn(f) = &m.items[0] else { panic!() };
7414        assert_eq!(f.annotations.len(), 1);
7415        assert_eq!(f.annotations[0].name.name, "deprecated");
7416        assert!(f.annotations[0].args.is_empty());
7417    }
7418
7419    #[test]
7420    fn annotation_positional_arg() {
7421        let src = "@domain(\"e-commerce\")\nfn process() {}\n";
7422        let (m, diags) = parse(src);
7423        assert!(!diags.has_errors(), "{diags:?}");
7424        let Item::Fn(f) = &m.items[0] else { panic!() };
7425        assert_eq!(f.annotations[0].name.name, "domain");
7426        assert_eq!(f.annotations[0].args.len(), 1);
7427    }
7428
7429    #[test]
7430    fn annotation_named_arg() {
7431        let src = "@performance(max_latency: 100)\nfn fast() {}\n";
7432        let (m, diags) = parse(src);
7433        assert!(!diags.has_errors(), "{diags:?}");
7434        let Item::Fn(f) = &m.items[0] else { panic!() };
7435        assert_eq!(f.annotations[0].name.name, "performance");
7436        assert_eq!(f.annotations[0].args.len(), 1);
7437    }
7438
7439    #[test]
7440    fn annotation_multiline_string() {
7441        let src = "@context(\"\"\"\n  Payment module.\n\"\"\")\nfn pay() {}\n";
7442        let (m, diags) = parse(src);
7443        assert!(!diags.has_errors(), "{diags:?}");
7444        let Item::Fn(f) = &m.items[0] else { panic!() };
7445        assert_eq!(f.annotations[0].name.name, "context");
7446        assert_eq!(f.annotations[0].args.len(), 1);
7447    }
7448
7449    #[test]
7450    fn multiple_annotations_stack() {
7451        let src = "@deprecated\n@domain(\"old\")\nfn legacy() {}\n";
7452        let (m, diags) = parse(src);
7453        assert!(!diags.has_errors(), "{diags:?}");
7454        let Item::Fn(f) = &m.items[0] else { panic!() };
7455        assert_eq!(f.annotations.len(), 2);
7456        assert_eq!(f.annotations[0].name.name, "deprecated");
7457        assert_eq!(f.annotations[1].name.name, "domain");
7458    }
7459
7460    // ─── Module handle declaration tests ─────────────────────────────────────
7461
7462    #[test]
7463    fn module_handle_decl() {
7464        let src = "handle Log with ConsoleLog\n";
7465        let (m, diags) = parse(src);
7466        assert!(!diags.has_errors(), "{diags:?}");
7467        assert_eq!(m.items.len(), 1);
7468        let Item::ModuleHandle(h) = &m.items[0] else {
7469            panic!("expected ModuleHandle")
7470        };
7471        assert_eq!(h.effect.segments[0].name, "Log");
7472    }
7473
7474    #[test]
7475    fn module_handle_decl_qualified_effect() {
7476        let src = "handle Std.Io with FileIo\n";
7477        let (m, diags) = parse(src);
7478        assert!(!diags.has_errors(), "{diags:?}");
7479        let Item::ModuleHandle(h) = &m.items[0] else {
7480            panic!("expected ModuleHandle")
7481        };
7482        assert_eq!(h.effect.segments.len(), 2);
7483        assert_eq!(h.effect.segments[0].name, "Std");
7484        assert_eq!(h.effect.segments[1].name, "Io");
7485    }
7486
7487    // ─── Effect clause integration test ──────────────────────────────────────
7488
7489    #[test]
7490    fn fn_with_effect_clause_integration() {
7491        let src = "fn process(data: Data) -> Result\n  with Log, Clock\n{\n  data\n}\n";
7492        let (m, diags) = parse(src);
7493        assert!(!diags.has_errors(), "{diags:?}");
7494        let Item::Fn(f) = &m.items[0] else { panic!() };
7495        assert_eq!(f.effect_clause.len(), 2);
7496        assert_eq!(f.effect_clause[0].segments[0].name, "Log");
7497        assert_eq!(f.effect_clause[1].segments[0].name, "Clock");
7498    }
7499
7500    // ─── P2.10: Disambiguation audit tests ───────────────────────────────────
7501
7502    /// Rule 1: `{` after TYPE_IDENT → record construction (not map or block).
7503    #[test]
7504    fn disambig_brace_after_type_ident_is_record_construct() {
7505        let src = "fn f() { Point { x: 1, y: 2 } }\n";
7506        let (m, diags) = parse(src);
7507        assert!(!diags.has_errors(), "{diags:?}");
7508        let Item::Fn(f) = &m.items[0] else { panic!() };
7509        let tail = f.body.as_ref().unwrap().tail.as_deref().expect("tail expr");
7510        assert!(
7511            matches!(tail, Expr::RecordConstruct { .. }),
7512            "expected RecordConstruct, got {tail:?}"
7513        );
7514    }
7515
7516    /// Rule 2: `{` with first element `expr ':'` → map literal.
7517    #[test]
7518    fn disambig_brace_with_colon_is_map() {
7519        let src = "fn f() { { \"key\": 42 } }\n";
7520        let (m, diags) = parse(src);
7521        assert!(!diags.has_errors(), "{diags:?}");
7522        let Item::Fn(f) = &m.items[0] else { panic!() };
7523        let tail = f.body.as_ref().unwrap().tail.as_deref().expect("tail expr");
7524        assert!(
7525            matches!(tail, Expr::MapLiteral { .. }),
7526            "expected MapLiteral, got {tail:?}"
7527        );
7528    }
7529
7530    /// Rule 3: `{` without colon after first element → block.
7531    #[test]
7532    fn disambig_brace_without_colon_is_block() {
7533        let src = "fn f() { { 42 } }\n";
7534        let (m, diags) = parse(src);
7535        assert!(!diags.has_errors(), "{diags:?}");
7536        let Item::Fn(f) = &m.items[0] else { panic!() };
7537        let tail = f.body.as_ref().unwrap().tail.as_deref().expect("tail expr");
7538        assert!(
7539            matches!(tail, Expr::Block { .. }),
7540            "expected Block, got {tail:?}"
7541        );
7542    }
7543
7544    /// Rule 4a: `(expr)` → grouped expression (not a tuple).
7545    #[test]
7546    fn disambig_single_paren_is_grouping() {
7547        let src = "fn f() { (42) }\n";
7548        let (m, diags) = parse(src);
7549        assert!(!diags.has_errors(), "{diags:?}");
7550        let Item::Fn(f) = &m.items[0] else { panic!() };
7551        let tail = f.body.as_ref().unwrap().tail.as_deref().expect("tail expr");
7552        // Grouped expression is returned as the inner expr (not TupleLiteral).
7553        assert!(
7554            !matches!(tail, Expr::TupleLiteral { .. }),
7555            "should not be TupleLiteral"
7556        );
7557    }
7558
7559    /// Rule 4b: `(expr, ...)` → tuple.
7560    #[test]
7561    fn disambig_multi_paren_is_tuple() {
7562        let src = "fn f() { (1, 2) }\n";
7563        let (m, diags) = parse(src);
7564        assert!(!diags.has_errors(), "{diags:?}");
7565        let Item::Fn(f) = &m.items[0] else { panic!() };
7566        let tail = f.body.as_ref().unwrap().tail.as_deref().expect("tail expr");
7567        assert!(matches!(tail, Expr::TupleLiteral { elems, .. } if elems.len() == 2));
7568    }
7569
7570    /// Rule 4c: trailing comma `(expr,)` → single-element tuple.
7571    #[test]
7572    fn disambig_trailing_comma_is_single_elem_tuple() {
7573        let src = "fn f() { (1,) }\n";
7574        let (m, diags) = parse(src);
7575        assert!(!diags.has_errors(), "{diags:?}");
7576        let Item::Fn(f) = &m.items[0] else { panic!() };
7577        let tail = f.body.as_ref().unwrap().tail.as_deref().expect("tail expr");
7578        assert!(matches!(tail, Expr::TupleLiteral { elems, .. } if elems.len() == 1));
7579    }
7580
7581    /// Rule: map literal with ident key (not just string keys).
7582    #[test]
7583    fn disambig_map_with_ident_key() {
7584        let src = "fn f() { { name: \"Alice\" } }\n";
7585        let (m, diags) = parse(src);
7586        assert!(!diags.has_errors(), "{diags:?}");
7587        let Item::Fn(f) = &m.items[0] else { panic!() };
7588        let tail = f.body.as_ref().unwrap().tail.as_deref().expect("tail expr");
7589        assert!(matches!(tail, Expr::MapLiteral { entries, .. } if entries.len() == 1));
7590    }
7591
7592    /// Empty map `{}` — no colon so it's an empty block, not a map.
7593    #[test]
7594    fn disambig_empty_braces_is_block() {
7595        let src = "fn f() { {} }\n";
7596        let (m, diags) = parse(src);
7597        assert!(!diags.has_errors(), "{diags:?}");
7598        let Item::Fn(f) = &m.items[0] else { panic!() };
7599        let tail = f.body.as_ref().unwrap().tail.as_deref().expect("tail expr");
7600        assert!(
7601            matches!(tail, Expr::Block { .. }),
7602            "expected Block, got {tail:?}"
7603        );
7604    }
7605
7606    // ─── P2.10: Error recovery tests ─────────────────────────────────────────
7607
7608    /// Parser recovers from an unexpected token at top level and continues
7609    /// parsing subsequent valid declarations.
7610    #[test]
7611    fn recovery_unexpected_token_at_top_level() {
7612        // `???` is not a valid declaration keyword
7613        let src = "fn before() {}\n???\nfn after() {}\n";
7614        let (m, diags) = parse(src);
7615        assert!(diags.has_errors(), "should have errors");
7616        // Both valid functions should still be parsed
7617        let fns: Vec<_> = m
7618            .items
7619            .iter()
7620            .filter(|i| matches!(i, Item::Fn(_)))
7621            .collect();
7622        assert_eq!(fns.len(), 2, "both fns should be in the AST");
7623        // Error node should be present
7624        assert!(m.items.iter().any(|i| matches!(i, Item::Error { .. })));
7625    }
7626
7627    /// Multiple errors in one file are all reported (not just the first).
7628    #[test]
7629    fn recovery_multiple_errors_reported() {
7630        // Each bad section is separated by a valid fn, forcing independent recoveries.
7631        let src = "???\nfn mid() {}\n!!!\nfn ok() {}\n";
7632        let (m, diags) = parse(src);
7633        assert!(diags.has_errors());
7634        assert!(
7635            diags.error_count() >= 2,
7636            "should report multiple errors, got: {diags:?}"
7637        );
7638        // Both valid fns should be recovered
7639        let fns: Vec<_> = m
7640            .items
7641            .iter()
7642            .filter(|i| matches!(i, Item::Fn(_)))
7643            .collect();
7644        assert_eq!(fns.len(), 2);
7645    }
7646
7647    /// Parser continues after a missing `}` closing a function body.
7648    #[test]
7649    fn recovery_after_malformed_fn_body() {
7650        // Missing closing brace on first fn; second fn should still parse.
7651        let src = "fn bad( {}\nfn good() {}\n";
7652        let (m, diags) = parse(src);
7653        assert!(diags.has_errors());
7654        // At least `good` should appear in items
7655        let has_good = m.items.iter().any(|i| {
7656            if let Item::Fn(f) = i {
7657                f.name.name == "good"
7658            } else {
7659                false
7660            }
7661        });
7662        assert!(
7663            has_good,
7664            "fn good should be recovered; items: {:#?}",
7665            m.items
7666        );
7667    }
7668
7669    // ─── P2.10: Integration test — complete multi-item source file ────────────
7670
7671    #[test]
7672    fn integration_complete_source_file() {
7673        let src = "\
7674module app.core\n\
7675use std.io.*\n\
7676use std.collections.{List, Map}\n\
7677\n\
7678public record User {\n\
7679  name: String\n\
7680  age: Int\n\
7681}\n\
7682\n\
7683public enum Color {\n\
7684  Red\n\
7685  Green\n\
7686  Blue\n\
7687}\n\
7688\n\
7689public fn greet(user: User) -> String {\n\
7690  \"Hello\"\n\
7691}\n\
7692\n\
7693public fn add(x: Int, y: Int) -> Int {\n\
7694  x + y\n\
7695}\n\
7696";
7697        let (m, diags) = parse(src);
7698        assert!(!diags.has_errors(), "errors: {diags:?}");
7699
7700        // Module declaration
7701        let path = m.path.as_ref().expect("module path");
7702        assert_eq!(path.segments[0].name, "app");
7703        assert_eq!(path.segments[1].name, "core");
7704
7705        // Two imports
7706        assert_eq!(m.imports.len(), 2);
7707
7708        // Items: record, enum, fn, fn
7709        assert_eq!(m.items.len(), 4);
7710        assert!(matches!(m.items[0], Item::Record(_)));
7711        assert!(matches!(m.items[1], Item::Enum(_)));
7712        assert!(matches!(m.items[2], Item::Fn(_)));
7713        assert!(matches!(m.items[3], Item::Fn(_)));
7714
7715        let Item::Record(r) = &m.items[0] else {
7716            panic!()
7717        };
7718        assert_eq!(r.name.name, "User");
7719        assert_eq!(r.fields.len(), 2);
7720
7721        let Item::Enum(e) = &m.items[1] else { panic!() };
7722        assert_eq!(e.name.name, "Color");
7723        assert_eq!(e.variants.len(), 3);
7724    }
7725
7726    // ─── Type alias tests ──────────────────────────────────────────────────
7727
7728    #[test]
7729    fn parse_type_alias_simple() {
7730        let (m, diags) = parse("type Email = String\n");
7731        assert!(!diags.has_errors(), "errors: {diags:?}");
7732        assert_eq!(m.items.len(), 1);
7733        let Item::TypeAlias(ta) = &m.items[0] else {
7734            panic!("expected TypeAlias")
7735        };
7736        assert_eq!(ta.name.name, "Email");
7737        assert!(ta.generic_params.is_empty());
7738        assert!(ta.where_clause.is_empty());
7739        assert_eq!(ta.visibility, Visibility::Private);
7740    }
7741
7742    #[test]
7743    fn parse_type_alias_generic() {
7744        let (m, diags) = parse("type NonEmpty[T] = List[T]\n");
7745        assert!(!diags.has_errors(), "errors: {diags:?}");
7746        assert_eq!(m.items.len(), 1);
7747        let Item::TypeAlias(ta) = &m.items[0] else {
7748            panic!("expected TypeAlias")
7749        };
7750        assert_eq!(ta.name.name, "NonEmpty");
7751        assert_eq!(ta.generic_params.len(), 1);
7752        assert_eq!(ta.generic_params[0].name.name, "T");
7753    }
7754
7755    #[test]
7756    fn parse_type_alias_plain() {
7757        let (m, diags) = parse("type Port = Int\n");
7758        assert!(!diags.has_errors(), "errors: {diags:?}");
7759        assert_eq!(m.items.len(), 1);
7760        let Item::TypeAlias(ta) = &m.items[0] else {
7761            panic!("expected TypeAlias")
7762        };
7763        assert_eq!(ta.name.name, "Port");
7764        assert!(ta.generic_params.is_empty());
7765    }
7766
7767    #[test]
7768    fn parse_type_alias_with_where_clause() {
7769        let (m, diags) = parse("type Sortable[T] = List[T] where (T: Comparable)\n");
7770        assert!(!diags.has_errors(), "errors: {diags:?}");
7771        assert_eq!(m.items.len(), 1);
7772        let Item::TypeAlias(ta) = &m.items[0] else {
7773            panic!("expected TypeAlias")
7774        };
7775        assert_eq!(ta.name.name, "Sortable");
7776        assert_eq!(ta.generic_params.len(), 1);
7777        assert_eq!(ta.where_clause.len(), 1);
7778        assert_eq!(ta.where_clause[0].param.name, "T");
7779    }
7780
7781    // ─── Const declaration tests ──────────────────────────────────────────
7782
7783    #[test]
7784    fn parse_const_int() {
7785        let (m, diags) = parse("const MAX_SIZE: Int = 1024\n");
7786        assert!(!diags.has_errors(), "errors: {diags:?}");
7787        assert_eq!(m.items.len(), 1);
7788        let Item::Const(cd) = &m.items[0] else {
7789            panic!("expected Const")
7790        };
7791        assert_eq!(cd.name.name, "MAX_SIZE");
7792        assert_eq!(cd.visibility, Visibility::Private);
7793    }
7794
7795    #[test]
7796    fn parse_const_float() {
7797        let (m, diags) = parse("const PI: Float = 3.14159\n");
7798        assert!(!diags.has_errors(), "errors: {diags:?}");
7799        assert_eq!(m.items.len(), 1);
7800        let Item::Const(cd) = &m.items[0] else {
7801            panic!("expected Const")
7802        };
7803        assert_eq!(cd.name.name, "PI");
7804    }
7805
7806    #[test]
7807    fn parse_const_with_visibility() {
7808        let (m, diags) = parse("public const VERSION: String = \"1.0.0\"\n");
7809        assert!(!diags.has_errors(), "errors: {diags:?}");
7810        assert_eq!(m.items.len(), 1);
7811        let Item::Const(cd) = &m.items[0] else {
7812            panic!("expected Const")
7813        };
7814        assert_eq!(cd.name.name, "VERSION");
7815        assert_eq!(cd.visibility, Visibility::Public);
7816    }
7817
7818    #[test]
7819    fn parse_type_alias_with_visibility() {
7820        let (m, diags) = parse("public type UserId = Int\n");
7821        assert!(!diags.has_errors(), "errors: {diags:?}");
7822        assert_eq!(m.items.len(), 1);
7823        let Item::TypeAlias(ta) = &m.items[0] else {
7824            panic!("expected TypeAlias")
7825        };
7826        assert_eq!(ta.name.name, "UserId");
7827        assert_eq!(ta.visibility, Visibility::Public);
7828    }
7829
7830    // ─── F2.02: Parser stores previously discarded data ─────────────────
7831
7832    #[test]
7833    fn import_visibility_stored() {
7834        let (m, diags) = parse("public use app.models.User\n");
7835        assert!(!diags.has_errors(), "errors: {diags:?}");
7836        assert_eq!(m.imports.len(), 1);
7837        assert_eq!(m.imports[0].visibility, Visibility::Public);
7838    }
7839
7840    #[test]
7841    fn import_private_visibility_default() {
7842        let (m, diags) = parse("use app.models.User\n");
7843        assert!(!diags.has_errors(), "errors: {diags:?}");
7844        assert_eq!(m.imports.len(), 1);
7845        assert_eq!(m.imports[0].visibility, Visibility::Private);
7846    }
7847
7848    #[test]
7849    fn composite_effect_components_stored() {
7850        let (m, diags) = parse("effect IO = Log + Clock + Storage\n");
7851        assert!(!diags.has_errors(), "errors: {diags:?}");
7852        assert_eq!(m.items.len(), 1);
7853        let Item::Effect(eff) = &m.items[0] else {
7854            panic!("expected Effect")
7855        };
7856        assert_eq!(eff.name.name, "IO");
7857        let component_names: Vec<&str> = eff
7858            .components
7859            .iter()
7860            .map(|c| c.segments[0].name.as_str())
7861            .collect();
7862        assert_eq!(component_names, ["Log", "Clock", "Storage"]);
7863    }
7864
7865    #[test]
7866    fn trait_supertraits_stored() {
7867        let (m, diags) = parse("trait Ordered: Comparable, Equatable {\n}\n");
7868        assert!(!diags.has_errors(), "errors: {diags:?}");
7869        assert_eq!(m.items.len(), 1);
7870        let Item::Trait(tr) = &m.items[0] else {
7871            panic!("expected Trait")
7872        };
7873        assert_eq!(tr.name.name, "Ordered");
7874        let supertrait_names: Vec<&str> = tr
7875            .supertraits
7876            .iter()
7877            .map(|s| s.segments[0].name.as_str())
7878            .collect();
7879        assert_eq!(supertrait_names, ["Comparable", "Equatable"]);
7880    }
7881
7882    #[test]
7883    fn import_alias_in_list() {
7884        let (m, diags) = parse("use json.{Value as JsonValue}\n");
7885        assert!(!diags.has_errors(), "errors: {diags:?}");
7886        assert_eq!(m.imports.len(), 1);
7887        match &m.imports[0].items {
7888            ImportItems::Named(names) => {
7889                assert_eq!(names.len(), 1);
7890                assert_eq!(names[0].name.name, "Value");
7891                assert_eq!(names[0].alias.as_ref().unwrap().name, "JsonValue");
7892            }
7893            other => panic!("expected Named import, got {other:?}"),
7894        }
7895    }
7896
7897    #[test]
7898    fn annotation_named_args_preserve_labels() {
7899        let (m, diags) = parse("@performance(max_latency: 100, max_memory: 50)\nfn fast() {}\n");
7900        assert!(!diags.has_errors(), "errors: {diags:?}");
7901        assert_eq!(m.items.len(), 1);
7902        let Item::Fn(f) = &m.items[0] else {
7903            panic!("expected Fn")
7904        };
7905        assert_eq!(f.annotations.len(), 1);
7906        let ann = &f.annotations[0];
7907        assert_eq!(ann.name.name, "performance");
7908        assert_eq!(ann.args.len(), 2);
7909        assert_eq!(ann.args[0].label.as_ref().unwrap().name, "max_latency");
7910        assert_eq!(ann.args[1].label.as_ref().unwrap().name, "max_memory");
7911    }
7912
7913    #[test]
7914    fn trait_required_method_body_is_none() {
7915        let (m, diags) = parse("trait Foo {\n  fn bar(self) -> Int\n}\n");
7916        assert!(!diags.has_errors(), "errors: {diags:?}");
7917        assert_eq!(m.items.len(), 1);
7918        let Item::Trait(tr) = &m.items[0] else {
7919            panic!("expected Trait")
7920        };
7921        assert_eq!(tr.methods.len(), 1);
7922        assert_eq!(tr.methods[0].name.name, "bar");
7923        assert!(
7924            tr.methods[0].body.is_none(),
7925            "required method should have body: None"
7926        );
7927    }
7928
7929    // ─── Systematic precedence & associativity tests (F3.02 / M-050 + M-053) ──
7930
7931    // --- M-050: Comparison operators are non-associative ---
7932
7933    #[test]
7934    fn comparison_chained_eq_is_error() {
7935        // `a == b == c` must be a parse error (non-associative)
7936        let (_, diags) = parse_expr_str("a == b == c");
7937        assert!(diags.has_errors(), "chained == must produce a parse error");
7938    }
7939
7940    #[test]
7941    fn comparison_chained_ne_is_error() {
7942        let (_, diags) = parse_expr_str("a != b != c");
7943        assert!(diags.has_errors(), "chained != must produce a parse error");
7944    }
7945
7946    #[test]
7947    fn comparison_chained_lt_gt_is_error() {
7948        let (_, diags) = parse_expr_str("a < b > c");
7949        assert!(diags.has_errors(), "chained < > must produce a parse error");
7950    }
7951
7952    #[test]
7953    fn comparison_chained_le_ge_is_error() {
7954        let (_, diags) = parse_expr_str("a <= b >= c");
7955        assert!(
7956            diags.has_errors(),
7957            "chained <= >= must produce a parse error"
7958        );
7959    }
7960
7961    #[test]
7962    fn comparison_single_eq_still_works() {
7963        let (e, diags) = parse_expr_str("a == b");
7964        assert!(!diags.has_errors(), "{diags:?}");
7965        assert!(matches!(e, Expr::Binary { op: BinOp::Eq, .. }));
7966    }
7967
7968    #[test]
7969    fn comparison_single_lt_still_works() {
7970        let (e, diags) = parse_expr_str("a < b");
7971        assert!(!diags.has_errors(), "{diags:?}");
7972        assert!(matches!(e, Expr::Binary { op: BinOp::Lt, .. }));
7973    }
7974
7975    // --- M-053: Precedence tests for all 15 levels ---
7976    // For each adjacent pair, the higher-precedence operator binds tighter.
7977
7978    #[test]
7979    fn prec_01_02_assignment_wraps_pipe() {
7980        // `a = b |> c` → Assign(a, Pipe(b, c))
7981        let (e, diags) = parse_expr_str("a = b |> c");
7982        assert!(!diags.has_errors(), "{diags:?}");
7983        match &e {
7984            Expr::Assign { value, .. } => {
7985                assert!(
7986                    matches!(value.as_ref(), Expr::Pipe { .. }),
7987                    "assignment RHS should be Pipe, got {value:?}"
7988                );
7989            }
7990            _ => panic!("expected Assign, got {e:?}"),
7991        }
7992    }
7993
7994    #[test]
7995    fn prec_02_03_pipe_wraps_compose() {
7996        // `a |> b >> c` → Pipe(a, Compose(b, c))
7997        let (e, diags) = parse_expr_str("a |> b >> c");
7998        assert!(!diags.has_errors(), "{diags:?}");
7999        match &e {
8000            Expr::Pipe { right, .. } => {
8001                assert!(
8002                    matches!(right.as_ref(), Expr::Compose { .. }),
8003                    "pipe RHS should be Compose, got {right:?}"
8004                );
8005            }
8006            _ => panic!("expected Pipe, got {e:?}"),
8007        }
8008    }
8009
8010    #[test]
8011    fn prec_03_04_compose_wraps_range() {
8012        // `a >> b .. c` → Compose(a, Range(b, c))
8013        let (e, diags) = parse_expr_str("a >> b .. c");
8014        assert!(!diags.has_errors(), "{diags:?}");
8015        match &e {
8016            Expr::Compose { right, .. } => {
8017                assert!(
8018                    matches!(right.as_ref(), Expr::Range { .. }),
8019                    "compose RHS should be Range, got {right:?}"
8020                );
8021            }
8022            _ => panic!("expected Compose, got {e:?}"),
8023        }
8024    }
8025
8026    #[test]
8027    fn prec_04_05_range_wraps_logical_or() {
8028        // `a .. b || c` → Range(a, Or(b, c))
8029        let (e, diags) = parse_expr_str("a .. b || c");
8030        assert!(!diags.has_errors(), "{diags:?}");
8031        match &e {
8032            Expr::Range { hi, .. } => {
8033                assert!(
8034                    matches!(hi.as_ref(), Expr::Binary { op: BinOp::Or, .. }),
8035                    "range hi should be Or, got {hi:?}"
8036                );
8037            }
8038            _ => panic!("expected Range, got {e:?}"),
8039        }
8040    }
8041
8042    #[test]
8043    fn prec_05_06_or_wraps_and() {
8044        // `a || b && c` → Or(a, And(b, c))
8045        let (e, diags) = parse_expr_str("a || b && c");
8046        assert!(!diags.has_errors(), "{diags:?}");
8047        match &e {
8048            Expr::Binary {
8049                op: BinOp::Or,
8050                right,
8051                ..
8052            } => {
8053                assert!(
8054                    matches!(right.as_ref(), Expr::Binary { op: BinOp::And, .. }),
8055                    "Or RHS should be And, got {right:?}"
8056                );
8057            }
8058            _ => panic!("expected Or, got {e:?}"),
8059        }
8060    }
8061
8062    #[test]
8063    fn prec_06_07_and_wraps_comparison() {
8064        // `a && b == c` → And(a, Eq(b, c))
8065        let (e, diags) = parse_expr_str("a && b == c");
8066        assert!(!diags.has_errors(), "{diags:?}");
8067        match &e {
8068            Expr::Binary {
8069                op: BinOp::And,
8070                right,
8071                ..
8072            } => {
8073                assert!(
8074                    matches!(right.as_ref(), Expr::Binary { op: BinOp::Eq, .. }),
8075                    "And RHS should be Eq, got {right:?}"
8076                );
8077            }
8078            _ => panic!("expected And, got {e:?}"),
8079        }
8080    }
8081
8082    #[test]
8083    fn prec_07_08_comparison_wraps_bitor() {
8084        // `a == b | c` → Eq(a, BitOr(b, c))
8085        let (e, diags) = parse_expr_str("a == b | c");
8086        assert!(!diags.has_errors(), "{diags:?}");
8087        match &e {
8088            Expr::Binary {
8089                op: BinOp::Eq,
8090                right,
8091                ..
8092            } => {
8093                assert!(
8094                    matches!(
8095                        right.as_ref(),
8096                        Expr::Binary {
8097                            op: BinOp::BitOr,
8098                            ..
8099                        }
8100                    ),
8101                    "Eq RHS should be BitOr, got {right:?}"
8102                );
8103            }
8104            _ => panic!("expected Eq, got {e:?}"),
8105        }
8106    }
8107
8108    #[test]
8109    fn prec_08_09_bitor_wraps_bitxor() {
8110        // `a | b ^ c` → BitOr(a, BitXor(b, c))
8111        let (e, diags) = parse_expr_str("a | b ^ c");
8112        assert!(!diags.has_errors(), "{diags:?}");
8113        match &e {
8114            Expr::Binary {
8115                op: BinOp::BitOr,
8116                right,
8117                ..
8118            } => {
8119                assert!(
8120                    matches!(
8121                        right.as_ref(),
8122                        Expr::Binary {
8123                            op: BinOp::BitXor,
8124                            ..
8125                        }
8126                    ),
8127                    "BitOr RHS should be BitXor, got {right:?}"
8128                );
8129            }
8130            _ => panic!("expected BitOr, got {e:?}"),
8131        }
8132    }
8133
8134    #[test]
8135    fn prec_09_10_bitxor_wraps_bitand() {
8136        // `a ^ b & c` → BitXor(a, BitAnd(b, c))
8137        let (e, diags) = parse_expr_str("a ^ b & c");
8138        assert!(!diags.has_errors(), "{diags:?}");
8139        match &e {
8140            Expr::Binary {
8141                op: BinOp::BitXor,
8142                right,
8143                ..
8144            } => {
8145                assert!(
8146                    matches!(
8147                        right.as_ref(),
8148                        Expr::Binary {
8149                            op: BinOp::BitAnd,
8150                            ..
8151                        }
8152                    ),
8153                    "BitXor RHS should be BitAnd, got {right:?}"
8154                );
8155            }
8156            _ => panic!("expected BitXor, got {e:?}"),
8157        }
8158    }
8159
8160    #[test]
8161    fn prec_10_11_bitand_wraps_add() {
8162        // `a & b + c` → BitAnd(a, Add(b, c))
8163        let (e, diags) = parse_expr_str("a & b + c");
8164        assert!(!diags.has_errors(), "{diags:?}");
8165        match &e {
8166            Expr::Binary {
8167                op: BinOp::BitAnd,
8168                right,
8169                ..
8170            } => {
8171                assert!(
8172                    matches!(right.as_ref(), Expr::Binary { op: BinOp::Add, .. }),
8173                    "BitAnd RHS should be Add, got {right:?}"
8174                );
8175            }
8176            _ => panic!("expected BitAnd, got {e:?}"),
8177        }
8178    }
8179
8180    #[test]
8181    fn prec_11_12_add_wraps_mul() {
8182        // `a + b * c` → Add(a, Mul(b, c))
8183        let (e, diags) = parse_expr_str("a + b * c");
8184        assert!(!diags.has_errors(), "{diags:?}");
8185        match &e {
8186            Expr::Binary {
8187                op: BinOp::Add,
8188                right,
8189                ..
8190            } => {
8191                assert!(
8192                    matches!(right.as_ref(), Expr::Binary { op: BinOp::Mul, .. }),
8193                    "Add RHS should be Mul, got {right:?}"
8194                );
8195            }
8196            _ => panic!("expected Add, got {e:?}"),
8197        }
8198    }
8199
8200    #[test]
8201    fn prec_12_13_mul_wraps_power() {
8202        // `a * b ** c` → Mul(a, Pow(b, c))
8203        let (e, diags) = parse_expr_str("a * b ** c");
8204        assert!(!diags.has_errors(), "{diags:?}");
8205        match &e {
8206            Expr::Binary {
8207                op: BinOp::Mul,
8208                right,
8209                ..
8210            } => {
8211                assert!(
8212                    matches!(right.as_ref(), Expr::Binary { op: BinOp::Pow, .. }),
8213                    "Mul RHS should be Pow, got {right:?}"
8214                );
8215            }
8216            _ => panic!("expected Mul, got {e:?}"),
8217        }
8218    }
8219
8220    #[test]
8221    fn prec_13_14_power_wraps_unary() {
8222        // `-a ** b` → Pow(Neg(a), b) — unary binds tighter than power
8223        let (e, diags) = parse_expr_str("-a ** b");
8224        assert!(!diags.has_errors(), "{diags:?}");
8225        match &e {
8226            Expr::Binary {
8227                op: BinOp::Pow,
8228                left,
8229                ..
8230            } => {
8231                assert!(
8232                    matches!(
8233                        left.as_ref(),
8234                        Expr::Unary {
8235                            op: UnaryOp::Neg,
8236                            ..
8237                        }
8238                    ),
8239                    "Pow LHS should be Neg, got {left:?}"
8240                );
8241            }
8242            _ => panic!("expected Pow, got {e:?}"),
8243        }
8244    }
8245
8246    #[test]
8247    fn prec_14_15_unary_wraps_postfix() {
8248        // `!a.b` → Not(FieldAccess(a, b)) — postfix binds tighter than unary
8249        let (e, diags) = parse_expr_str("!a.b");
8250        assert!(!diags.has_errors(), "{diags:?}");
8251        match &e {
8252            Expr::Unary {
8253                op: UnaryOp::Not,
8254                operand,
8255                ..
8256            } => {
8257                assert!(
8258                    matches!(operand.as_ref(), Expr::FieldAccess { .. }),
8259                    "Not operand should be FieldAccess, got {operand:?}"
8260                );
8261            }
8262            _ => panic!("expected Unary(Not), got {e:?}"),
8263        }
8264    }
8265
8266    // --- Associativity tests for each level ---
8267
8268    #[test]
8269    fn assoc_01_assignment_right() {
8270        // `a = b = c` → Assign(a, Assign(b, c)) — right-associative
8271        let (e, diags) = parse_expr_str("a = b = c");
8272        assert!(!diags.has_errors(), "{diags:?}");
8273        match &e {
8274            Expr::Assign { value, .. } => {
8275                assert!(
8276                    matches!(value.as_ref(), Expr::Assign { .. }),
8277                    "assignment should be right-assoc, got {value:?}"
8278                );
8279            }
8280            _ => panic!("expected Assign, got {e:?}"),
8281        }
8282    }
8283
8284    #[test]
8285    fn assoc_02_pipe_left() {
8286        // `a |> b |> c` → Pipe(Pipe(a, b), c) — left-associative
8287        let (e, diags) = parse_expr_str("a |> b |> c");
8288        assert!(!diags.has_errors(), "{diags:?}");
8289        match &e {
8290            Expr::Pipe { left, .. } => {
8291                assert!(
8292                    matches!(left.as_ref(), Expr::Pipe { .. }),
8293                    "pipe should be left-assoc, got {left:?}"
8294                );
8295            }
8296            _ => panic!("expected Pipe, got {e:?}"),
8297        }
8298    }
8299
8300    #[test]
8301    fn assoc_03_compose_left() {
8302        // `a >> b >> c` → Compose(Compose(a, b), c) — left-associative
8303        let (e, diags) = parse_expr_str("a >> b >> c");
8304        assert!(!diags.has_errors(), "{diags:?}");
8305        match &e {
8306            Expr::Compose { left, .. } => {
8307                assert!(
8308                    matches!(left.as_ref(), Expr::Compose { .. }),
8309                    "compose should be left-assoc, got {left:?}"
8310                );
8311            }
8312            _ => panic!("expected Compose, got {e:?}"),
8313        }
8314    }
8315
8316    #[test]
8317    fn assoc_04_range_non_assoc() {
8318        // `a .. b .. c` must be a parse error (non-associative)
8319        let (_, diags) = parse_expr_str("a .. b .. c");
8320        assert!(diags.has_errors(), "chained .. must produce a parse error");
8321    }
8322
8323    #[test]
8324    fn assoc_05_or_left() {
8325        // `a || b || c` → Or(Or(a, b), c) — left-associative
8326        let (e, diags) = parse_expr_str("a || b || c");
8327        assert!(!diags.has_errors(), "{diags:?}");
8328        match &e {
8329            Expr::Binary {
8330                op: BinOp::Or,
8331                left,
8332                ..
8333            } => {
8334                assert!(
8335                    matches!(left.as_ref(), Expr::Binary { op: BinOp::Or, .. }),
8336                    "|| should be left-assoc, got {left:?}"
8337                );
8338            }
8339            _ => panic!("expected Or, got {e:?}"),
8340        }
8341    }
8342
8343    #[test]
8344    fn assoc_06_and_left() {
8345        // `a && b && c` → And(And(a, b), c) — left-associative
8346        let (e, diags) = parse_expr_str("a && b && c");
8347        assert!(!diags.has_errors(), "{diags:?}");
8348        match &e {
8349            Expr::Binary {
8350                op: BinOp::And,
8351                left,
8352                ..
8353            } => {
8354                assert!(
8355                    matches!(left.as_ref(), Expr::Binary { op: BinOp::And, .. }),
8356                    "&& should be left-assoc, got {left:?}"
8357                );
8358            }
8359            _ => panic!("expected And, got {e:?}"),
8360        }
8361    }
8362
8363    #[test]
8364    fn assoc_07_comparison_non_assoc() {
8365        // `a == b == c` must be a parse error (non-associative) — same as M-050 test
8366        let (_, diags) = parse_expr_str("a == b == c");
8367        assert!(diags.has_errors(), "chained == must produce a parse error");
8368    }
8369
8370    #[test]
8371    fn assoc_08_bitor_left() {
8372        // `a | b | c` → BitOr(BitOr(a, b), c) — left-associative
8373        let (e, diags) = parse_expr_str("a | b | c");
8374        assert!(!diags.has_errors(), "{diags:?}");
8375        match &e {
8376            Expr::Binary {
8377                op: BinOp::BitOr,
8378                left,
8379                ..
8380            } => {
8381                assert!(
8382                    matches!(
8383                        left.as_ref(),
8384                        Expr::Binary {
8385                            op: BinOp::BitOr,
8386                            ..
8387                        }
8388                    ),
8389                    "| should be left-assoc, got {left:?}"
8390                );
8391            }
8392            _ => panic!("expected BitOr, got {e:?}"),
8393        }
8394    }
8395
8396    #[test]
8397    fn assoc_09_bitxor_left() {
8398        // `a ^ b ^ c` → BitXor(BitXor(a, b), c) — left-associative
8399        let (e, diags) = parse_expr_str("a ^ b ^ c");
8400        assert!(!diags.has_errors(), "{diags:?}");
8401        match &e {
8402            Expr::Binary {
8403                op: BinOp::BitXor,
8404                left,
8405                ..
8406            } => {
8407                assert!(
8408                    matches!(
8409                        left.as_ref(),
8410                        Expr::Binary {
8411                            op: BinOp::BitXor,
8412                            ..
8413                        }
8414                    ),
8415                    "^ should be left-assoc, got {left:?}"
8416                );
8417            }
8418            _ => panic!("expected BitXor, got {e:?}"),
8419        }
8420    }
8421
8422    #[test]
8423    fn assoc_10_bitand_left() {
8424        // `a & b & c` → BitAnd(BitAnd(a, b), c) — left-associative
8425        let (e, diags) = parse_expr_str("a & b & c");
8426        assert!(!diags.has_errors(), "{diags:?}");
8427        match &e {
8428            Expr::Binary {
8429                op: BinOp::BitAnd,
8430                left,
8431                ..
8432            } => {
8433                assert!(
8434                    matches!(
8435                        left.as_ref(),
8436                        Expr::Binary {
8437                            op: BinOp::BitAnd,
8438                            ..
8439                        }
8440                    ),
8441                    "& should be left-assoc, got {left:?}"
8442                );
8443            }
8444            _ => panic!("expected BitAnd, got {e:?}"),
8445        }
8446    }
8447
8448    #[test]
8449    fn assoc_11_add_left() {
8450        // `a - b - c` → Sub(Sub(a, b), c) — left-associative
8451        let (e, diags) = parse_expr_str("a - b - c");
8452        assert!(!diags.has_errors(), "{diags:?}");
8453        match &e {
8454            Expr::Binary {
8455                op: BinOp::Sub,
8456                left,
8457                ..
8458            } => {
8459                assert!(
8460                    matches!(left.as_ref(), Expr::Binary { op: BinOp::Sub, .. }),
8461                    "- should be left-assoc, got {left:?}"
8462                );
8463            }
8464            _ => panic!("expected Sub, got {e:?}"),
8465        }
8466    }
8467
8468    #[test]
8469    fn assoc_12_mul_left() {
8470        // `a / b / c` → Div(Div(a, b), c) — left-associative
8471        let (e, diags) = parse_expr_str("a / b / c");
8472        assert!(!diags.has_errors(), "{diags:?}");
8473        match &e {
8474            Expr::Binary {
8475                op: BinOp::Div,
8476                left,
8477                ..
8478            } => {
8479                assert!(
8480                    matches!(left.as_ref(), Expr::Binary { op: BinOp::Div, .. }),
8481                    "/ should be left-assoc, got {left:?}"
8482                );
8483            }
8484            _ => panic!("expected Div, got {e:?}"),
8485        }
8486    }
8487
8488    #[test]
8489    fn assoc_13_power_right() {
8490        // `a ** b ** c` → Pow(a, Pow(b, c)) — right-associative
8491        let (e, diags) = parse_expr_str("a ** b ** c");
8492        assert!(!diags.has_errors(), "{diags:?}");
8493        match &e {
8494            Expr::Binary {
8495                op: BinOp::Pow,
8496                right,
8497                ..
8498            } => {
8499                assert!(
8500                    matches!(right.as_ref(), Expr::Binary { op: BinOp::Pow, .. }),
8501                    "** should be right-assoc, got {right:?}"
8502                );
8503            }
8504            _ => panic!("expected Pow, got {e:?}"),
8505        }
8506    }
8507
8508    #[test]
8509    fn assoc_14_unary_chains() {
8510        // `--a` → Neg(Neg(a)) — unary prefix naturally chains right
8511        let (e, diags) = parse_expr_str("--a");
8512        assert!(!diags.has_errors(), "{diags:?}");
8513        match &e {
8514            Expr::Unary {
8515                op: UnaryOp::Neg,
8516                operand,
8517                ..
8518            } => {
8519                assert!(
8520                    matches!(
8521                        operand.as_ref(),
8522                        Expr::Unary {
8523                            op: UnaryOp::Neg,
8524                            ..
8525                        }
8526                    ),
8527                    "unary should chain, got {operand:?}"
8528                );
8529            }
8530            _ => panic!("expected Neg(Neg), got {e:?}"),
8531        }
8532    }
8533
8534    #[test]
8535    fn assoc_15_postfix_chains_left() {
8536        // `a.b.c` → FieldAccess(FieldAccess(a, b), c) — postfix chains left
8537        let (e, diags) = parse_expr_str("a.b.c");
8538        assert!(!diags.has_errors(), "{diags:?}");
8539        match &e {
8540            Expr::FieldAccess { field, object, .. } => {
8541                assert_eq!(field.name, "c");
8542                assert!(
8543                    matches!(object.as_ref(), Expr::FieldAccess { .. }),
8544                    "postfix should chain left, got {object:?}"
8545                );
8546            }
8547            _ => panic!("expected FieldAccess, got {e:?}"),
8548        }
8549    }
8550
8551    // ── F3.03: Module-qualified record construction (M-052) ──────────────
8552
8553    #[test]
8554    fn module_qualified_record_construct() {
8555        // `Mod.Type { field: val }` should parse as RecordConstruct with two-segment path
8556        let (e, diags) = parse_expr_str("Mod.Type { field: val }");
8557        assert!(!diags.has_errors(), "{diags:?}");
8558        match e {
8559            Expr::RecordConstruct { path, fields, .. } => {
8560                assert_eq!(path.segments.len(), 2);
8561                assert_eq!(path.segments[0].name, "Mod");
8562                assert_eq!(path.segments[1].name, "Type");
8563                assert_eq!(fields.len(), 1);
8564                assert_eq!(fields[0].name.name, "field");
8565            }
8566            _ => panic!("expected RecordConstruct, got {e:?}"),
8567        }
8568    }
8569
8570    #[test]
8571    fn deeply_qualified_record_construct() {
8572        // `A.B.C { x: 1 }` — three-segment path
8573        let (e, diags) = parse_expr_str("A.B.C { x: 1 }");
8574        assert!(!diags.has_errors(), "{diags:?}");
8575        match e {
8576            Expr::RecordConstruct { path, fields, .. } => {
8577                assert_eq!(path.segments.len(), 3);
8578                assert_eq!(path.segments[0].name, "A");
8579                assert_eq!(path.segments[1].name, "B");
8580                assert_eq!(path.segments[2].name, "C");
8581                assert_eq!(fields.len(), 1);
8582            }
8583            _ => panic!("expected RecordConstruct, got {e:?}"),
8584        }
8585    }
8586
8587    #[test]
8588    fn lowercase_field_access_not_record() {
8589        // `Mod.field { ... }` — `field` is lowercase, so this should NOT
8590        // be record construction (it's a field access followed by a block).
8591        let (e, diags) = parse_expr_str("Mod.field");
8592        assert!(!diags.has_errors(), "{diags:?}");
8593        assert!(
8594            matches!(e, Expr::FieldAccess { .. }),
8595            "expected FieldAccess, got {e:?}"
8596        );
8597    }
8598
8599    // ── F3.03: Method-level type arguments (M-051) ──────────────────────
8600
8601    #[test]
8602    fn method_type_args_single() {
8603        // `obj.method[T]()` should parse as MethodCall with type_args
8604        let (e, diags) = parse_expr_str("obj.method[T]()");
8605        assert!(!diags.has_errors(), "{diags:?}");
8606        match e {
8607            Expr::MethodCall {
8608                method,
8609                type_args,
8610                args,
8611                ..
8612            } => {
8613                assert_eq!(method.name, "method");
8614                assert_eq!(type_args.len(), 1);
8615                assert!(args.is_empty());
8616            }
8617            _ => panic!("expected MethodCall, got {e:?}"),
8618        }
8619    }
8620
8621    #[test]
8622    fn method_type_args_multiple() {
8623        // `obj.convert[From, To](x)` — multiple type args
8624        let (e, diags) = parse_expr_str("obj.convert[From, To](x)");
8625        assert!(!diags.has_errors(), "{diags:?}");
8626        match e {
8627            Expr::MethodCall {
8628                method,
8629                type_args,
8630                args,
8631                ..
8632            } => {
8633                assert_eq!(method.name, "convert");
8634                assert_eq!(type_args.len(), 2);
8635                assert_eq!(args.len(), 1);
8636            }
8637            _ => panic!("expected MethodCall, got {e:?}"),
8638        }
8639    }
8640
8641    #[test]
8642    fn index_access_not_type_args() {
8643        // `obj.data[0]` — numeric index, should remain as index access
8644        let (e, diags) = parse_expr_str("obj.data[0]");
8645        assert!(!diags.has_errors(), "{diags:?}");
8646        // Should be Index(FieldAccess(obj, data), 0)
8647        assert!(matches!(e, Expr::Index { .. }), "expected Index, got {e:?}");
8648    }
8649
8650    // ─── Doc comment tests (F0.04) ──────────────────────────────────────────
8651
8652    #[test]
8653    fn doc_comment_before_fn_no_error() {
8654        let (m, diags) = parse("/// Adds two numbers\nfn add(a: Int, b: Int) -> Int { a + b }\n");
8655        assert!(
8656            !diags.has_errors(),
8657            "doc comment before fn caused errors: {diags:?}"
8658        );
8659        assert_eq!(m.items.len(), 1);
8660        assert!(matches!(m.items[0], Item::Fn(_)));
8661    }
8662
8663    #[test]
8664    fn doc_comment_before_record_no_error() {
8665        let (m, diags) = parse("/// A user\nrecord User {\n  name: String\n}\n");
8666        assert!(
8667            !diags.has_errors(),
8668            "doc comment before record caused errors: {diags:?}"
8669        );
8670        assert_eq!(m.items.len(), 1);
8671        assert!(matches!(m.items[0], Item::Record(_)));
8672    }
8673
8674    #[test]
8675    fn doc_comment_before_enum_no_error() {
8676        let (m, diags) = parse("/// Colors\nenum Color {\n  Red\n  Green\n}\n");
8677        assert!(
8678            !diags.has_errors(),
8679            "doc comment before enum caused errors: {diags:?}"
8680        );
8681        assert_eq!(m.items.len(), 1);
8682        assert!(matches!(m.items[0], Item::Enum(_)));
8683    }
8684
8685    #[test]
8686    fn doc_comment_before_trait_no_error() {
8687        let (m, diags) =
8688            parse("/// Greetable things\ntrait Greetable {\n  fn greet() -> String\n}\n");
8689        assert!(
8690            !diags.has_errors(),
8691            "doc comment before trait caused errors: {diags:?}"
8692        );
8693        assert_eq!(m.items.len(), 1);
8694        assert!(matches!(m.items[0], Item::Trait(_)));
8695    }
8696
8697    #[test]
8698    fn multiple_consecutive_doc_comments() {
8699        let (m, diags) = parse("/// Line 1\n/// Line 2\n/// Line 3\nfn foo() {}\n");
8700        assert!(
8701            !diags.has_errors(),
8702            "multiple doc comments caused errors: {diags:?}"
8703        );
8704        assert_eq!(m.items.len(), 1);
8705        assert!(matches!(m.items[0], Item::Fn(_)));
8706    }
8707
8708    #[test]
8709    fn module_doc_comment_no_error() {
8710        let (m, diags) = parse("//! Module docs\n\nfn foo() {}\n");
8711        assert!(
8712            !diags.has_errors(),
8713            "module doc comment caused errors: {diags:?}"
8714        );
8715        assert_eq!(m.doc.len(), 1);
8716        assert_eq!(m.doc[0], "Module docs");
8717    }
8718
8719    #[test]
8720    fn module_doc_comment_after_module_decl() {
8721        // FC-26: //! after `module` declaration should be valid.
8722        let (m, diags) = parse("module foo\n\n//! After module\n//! More docs\n\nfn bar() {}\n");
8723        assert!(
8724            !diags.has_errors(),
8725            "//! after module decl caused errors: {diags:?}"
8726        );
8727        assert_eq!(m.doc.len(), 2);
8728        assert_eq!(m.doc[0], "After module");
8729        assert_eq!(m.doc[1], "More docs");
8730        assert_eq!(m.items.len(), 1);
8731    }
8732
8733    #[test]
8734    fn module_doc_comment_before_and_after_module_decl() {
8735        // FC-26: //! in both positions should merge into one doc list.
8736        let (m, diags) = parse("//! Before\nmodule foo\n//! After\nfn bar() {}\n");
8737        assert!(
8738            !diags.has_errors(),
8739            "//! before+after module decl caused errors: {diags:?}"
8740        );
8741        assert_eq!(m.doc.len(), 2);
8742        assert_eq!(m.doc[0], "Before");
8743        assert_eq!(m.doc[1], "After");
8744    }
8745
8746    #[test]
8747    fn module_doc_comment_after_module_with_use_no_hang() {
8748        // FC-26: //! after module + use on next line must not hang.
8749        let (m, diags) = parse("module foo\n//! Docs\nuse bar.{baz}\nfn f() {}\n");
8750        assert!(
8751            !diags.has_errors(),
8752            "//! after module with use caused errors: {diags:?}"
8753        );
8754        assert_eq!(m.doc.len(), 1);
8755        assert_eq!(m.imports.len(), 1);
8756    }
8757
8758    #[test]
8759    fn doc_comment_trailing_no_error() {
8760        // Doc comment at end of file with no following item
8761        let (_m, diags) = parse("/// orphan doc\n");
8762        assert!(
8763            !diags.has_errors(),
8764            "trailing doc comment caused errors: {diags:?}"
8765        );
8766    }
8767}