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