Skip to main content

gruel_parser/
chumsky_parser.rs

1//! Chumsky-based parser for the Gruel programming language.
2//!
3//! This module provides a parser implementation using chumsky combinators
4//! with Pratt parsing for expression precedence.
5
6use crate::ast::{
7    AnonFnExpr, AnonStructField, ArgMode, ArrayLitExpr, AssignStatement, AssignTarget,
8    AssocFnCallExpr, Ast, BinaryExpr, BinaryOp, BlockExpr, BoolLit, BreakExpr, CallArg, CallExpr,
9    CharLit, CheckedBlockExpr, ComptimeBlockExpr, ComptimeUnrollForExpr, ConstDecl, ContinueExpr,
10    DeriveDecl, Directive, DirectiveArg, Directives, Doc, EnumDecl, EnumStructLitExpr, EnumVariant,
11    Expr, FieldDecl, FieldExpr, FieldInit, FieldPattern, FloatLit, ForExpr, Function, Ident,
12    IfExpr, IndexExpr, IntLit, InterfaceDecl, IntrinsicArg, IntrinsicCallExpr, Item, LetStatement,
13    LoopExpr, MatchArm, MatchExpr, Method, MethodCallExpr, MethodSig, NegIntLit, Param, ParamMode,
14    ParenExpr, PathExpr, PathPattern, Pattern, RangeExpr, ReturnExpr, SelfExpr, SelfParam,
15    SelfReceiverKind, Statement, StringLit, StructDecl, StructLitExpr, TupleElemPattern, TupleExpr,
16    TupleIndexExpr, TypeExpr, TypeLitExpr, UnaryExpr, UnaryOp, UnitLit, Visibility, WhileExpr,
17};
18use chumsky::input::{Input as ChumskyInput, MapExtra, Stream, ValueInput};
19use chumsky::prelude::*;
20use chumsky::recovery::via_parser;
21use chumsky::recursive::Direct;
22use gruel_builtins::Posture;
23use gruel_lexer::TokenKind;
24use gruel_util::span::LineIndex;
25use gruel_util::{CompileError, CompileErrors, ErrorKind, MultiErrorResult, PreviewFeatures};
26use gruel_util::{FileId, Span};
27use lasso::{Spur, ThreadedRodeo};
28use std::borrow::Cow;
29use std::collections::HashMap;
30
31use chumsky::extra::SimpleState;
32
33/// Pre-interned symbols for primitive type names and special keywords.
34/// These are interned once when the parser is created and reused for all parsing.
35#[derive(Clone, Copy)]
36pub struct PrimitiveTypeSpurs {
37    pub i8: Spur,
38    pub i16: Spur,
39    pub i32: Spur,
40    pub i64: Spur,
41    pub isize: Spur,
42    pub u8: Spur,
43    pub u16: Spur,
44    pub u32: Spur,
45    pub u64: Spur,
46    pub usize: Spur,
47    pub f16: Spur,
48    pub f32: Spur,
49    pub f64: Spur,
50    pub bool: Spur,
51    /// Unicode scalar value type (ADR-0071)
52    pub char: Spur,
53    /// Self type keyword - used in methods to refer to the containing struct type
54    pub self_type: Spur,
55    /// `Ref` identifier (ADR-0076) — used by self-param parsing to
56    /// recognise `self : Ref(Self)` without a separate keyword token.
57    pub ref_name: Spur,
58    /// `MutRef` identifier (ADR-0076) — likewise for `self : MutRef(Self)`.
59    pub mut_ref_name: Spur,
60    /// The identifier "derive" — accepted as a directive name so users can
61    /// write `@derive(Foo)` even though `derive` is also a reserved keyword
62    /// for top-level derive items (ADR-0058).
63    pub derive_name: Spur,
64    /// The identifier "mark" — used to detect `@mark(unchecked)` in a
65    /// function/method directive list without a string compare.
66    pub mark_name: Spur,
67    /// The identifier "unchecked" — argument to `@mark(...)` recognised
68    /// at parse time so the parsed `Function::is_unchecked` /
69    /// `Method::is_unchecked` flag captures the directive form alongside
70    /// the legacy `unchecked` keyword (ADR-0088).
71    pub unchecked_name: Spur,
72}
73
74impl PrimitiveTypeSpurs {
75    /// Create a new set of primitive type symbols by interning them.
76    pub fn new(interner: &mut ThreadedRodeo) -> Self {
77        Self {
78            i8: interner.get_or_intern("i8"),
79            i16: interner.get_or_intern("i16"),
80            i32: interner.get_or_intern("i32"),
81            i64: interner.get_or_intern("i64"),
82            isize: interner.get_or_intern("isize"),
83            u8: interner.get_or_intern("u8"),
84            u16: interner.get_or_intern("u16"),
85            u32: interner.get_or_intern("u32"),
86            u64: interner.get_or_intern("u64"),
87            usize: interner.get_or_intern("usize"),
88            f16: interner.get_or_intern("f16"),
89            f32: interner.get_or_intern("f32"),
90            f64: interner.get_or_intern("f64"),
91            bool: interner.get_or_intern("bool"),
92            char: interner.get_or_intern("char"),
93            self_type: interner.get_or_intern("Self"),
94            ref_name: interner.get_or_intern("Ref"),
95            mut_ref_name: interner.get_or_intern("MutRef"),
96            derive_name: interner.get_or_intern("derive"),
97            mark_name: interner.get_or_intern("mark"),
98            unchecked_name: interner.get_or_intern("unchecked"),
99        }
100    }
101}
102
103/// ADR-0088: scan a directive list for `@mark(unchecked)`. Returns true
104/// if any directive in the list is `@mark(...)` with `unchecked` as one
105/// of its arguments. The lookup is by interned Spur, not string compare.
106pub(crate) fn directives_have_mark_unchecked(
107    directives: &Directives,
108    mark_name: Spur,
109    unchecked_name: Spur,
110) -> bool {
111    directives.iter().any(|d| {
112        d.name.name == mark_name
113            && d.args.iter().any(|a| match a {
114                DirectiveArg::Ident(ident) => ident.name == unchecked_name,
115                DirectiveArg::String(_) => false,
116            })
117    })
118}
119
120/// Parser state containing primitive type symbols and file ID.
121///
122/// This struct holds mutable state needed during parsing:
123/// - Pre-interned primitive type symbols for efficient lookup
124/// - The FileId for the current file being parsed (for multi-file compilation)
125#[derive(Clone, Copy)]
126pub struct ParserState {
127    /// Pre-interned primitive type symbols.
128    pub syms: PrimitiveTypeSpurs,
129    /// The file ID for spans in this file.
130    pub file_id: FileId,
131}
132
133impl ParserState {
134    /// Create a new parser state with the given symbols and file ID.
135    pub fn new(syms: PrimitiveTypeSpurs, file_id: FileId) -> Self {
136        Self { syms, file_id }
137    }
138}
139
140/// Type alias for parser extras that carries parser state.
141/// This replaces the previous thread-local approach with compile-time safe state passing.
142type ParserExtras<'src> = extra::Full<Rich<'src, TokenKind>, SimpleState<ParserState>, ()>;
143
144/// Type-erased parser to keep symbol names short. Boxing at each function boundary
145/// prevents monomorphized type chains from growing exponentially in length.
146type GruelParser<'src, I, O> = Boxed<'src, 'src, I, O, ParserExtras<'src>>;
147
148/// Convert a `usize` offset to `u32`, asserting it fits in debug builds.
149///
150/// # Panics
151///
152/// In debug builds, panics if `offset` exceeds `u32::MAX`.
153/// This would only happen for source files larger than 4GB.
154#[inline]
155fn offset_to_u32(offset: usize) -> u32 {
156    debug_assert!(
157        offset <= u32::MAX as usize,
158        "offset {} exceeds u32::MAX (source file too large)",
159        offset
160    );
161    offset as u32
162}
163
164/// Convert chumsky SimpleSpan to gruel_util::Span with a specific file ID.
165///
166/// # Panics
167///
168/// In debug builds, panics if `span.start` or `span.end` exceeds `u32::MAX`.
169/// This would only happen for source files larger than 4GB.
170fn to_gruel_util_with_file(span: SimpleSpan, file_id: FileId) -> Span {
171    Span::with_file(file_id, offset_to_u32(span.start), offset_to_u32(span.end))
172}
173
174/// Convert chumsky SimpleSpan to gruel_util::Span using the default file ID.
175/// Only used for error conversion where we don't have access to the parser state.
176fn to_gruel_util(span: SimpleSpan) -> Span {
177    Span::new(offset_to_u32(span.start), offset_to_u32(span.end))
178}
179
180/// Extract a Span with file ID from the parser extra.
181/// This is the primary way to create spans during parsing.
182#[inline]
183fn span_from_extra<'src, I>(e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>) -> Span
184where
185    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
186{
187    let file_id = e.state().0.file_id;
188    to_gruel_util_with_file(e.span(), file_id)
189}
190
191/// Parser that produces Ident from identifier tokens
192fn ident_parser<'src, I>() -> GruelParser<'src, I, Ident>
193where
194    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
195{
196    select! {
197        TokenKind::Ident(name) = e => Ident {
198            name,
199            span: span_from_extra(e),
200        },
201    }
202    .boxed()
203}
204
205/// Parser that produces Ident for a method name.
206///
207/// Kept as a separate function from `ident_parser` so callers can
208/// document method-position semantics, even though after retiring the
209/// `drop` keyword (it is now a regular identifier — `drop fn TypeName`
210/// at item position is recognized via a state-based ident match) the
211/// two are equivalent.
212fn method_name_parser<'src, I>() -> GruelParser<'src, I, Ident>
213where
214    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
215{
216    ident_parser().boxed()
217}
218
219/// Parser for primitive type keywords: i8, i16, i32, i64, u8, u16, u32, u64, bool
220/// These are reserved keywords that cannot be used as identifiers.
221fn primitive_type_parser<'src, I>() -> GruelParser<'src, I, TypeExpr>
222where
223    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
224{
225    // Access primitive type symbols from parser state via e.state().0
226    // SimpleState<T> wraps T in a .0 field
227    // Type annotation on closure parameter is needed to help Rust infer the Extra type
228    let i8_parser =
229        just(TokenKind::I8).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
230            let syms = e.state().0.syms;
231            TypeExpr::Named(Ident {
232                name: syms.i8,
233                span: span_from_extra(e),
234            })
235        });
236    let i16_parser =
237        just(TokenKind::I16).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
238            let syms = e.state().0.syms;
239            TypeExpr::Named(Ident {
240                name: syms.i16,
241                span: span_from_extra(e),
242            })
243        });
244    let i32_parser =
245        just(TokenKind::I32).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
246            let syms = e.state().0.syms;
247            TypeExpr::Named(Ident {
248                name: syms.i32,
249                span: span_from_extra(e),
250            })
251        });
252    let i64_parser =
253        just(TokenKind::I64).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
254            let syms = e.state().0.syms;
255            TypeExpr::Named(Ident {
256                name: syms.i64,
257                span: span_from_extra(e),
258            })
259        });
260    let u8_parser =
261        just(TokenKind::U8).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
262            let syms = e.state().0.syms;
263            TypeExpr::Named(Ident {
264                name: syms.u8,
265                span: span_from_extra(e),
266            })
267        });
268    let u16_parser =
269        just(TokenKind::U16).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
270            let syms = e.state().0.syms;
271            TypeExpr::Named(Ident {
272                name: syms.u16,
273                span: span_from_extra(e),
274            })
275        });
276    let u32_parser =
277        just(TokenKind::U32).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
278            let syms = e.state().0.syms;
279            TypeExpr::Named(Ident {
280                name: syms.u32,
281                span: span_from_extra(e),
282            })
283        });
284    let u64_parser =
285        just(TokenKind::U64).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
286            let syms = e.state().0.syms;
287            TypeExpr::Named(Ident {
288                name: syms.u64,
289                span: span_from_extra(e),
290            })
291        });
292    let isize_parser =
293        just(TokenKind::Isize).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
294            let syms = e.state().0.syms;
295            TypeExpr::Named(Ident {
296                name: syms.isize,
297                span: span_from_extra(e),
298            })
299        });
300    let usize_parser =
301        just(TokenKind::Usize).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
302            let syms = e.state().0.syms;
303            TypeExpr::Named(Ident {
304                name: syms.usize,
305                span: span_from_extra(e),
306            })
307        });
308    let f16_parser =
309        just(TokenKind::F16).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
310            let syms = e.state().0.syms;
311            TypeExpr::Named(Ident {
312                name: syms.f16,
313                span: span_from_extra(e),
314            })
315        });
316    let f32_parser =
317        just(TokenKind::F32).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
318            let syms = e.state().0.syms;
319            TypeExpr::Named(Ident {
320                name: syms.f32,
321                span: span_from_extra(e),
322            })
323        });
324    let f64_parser =
325        just(TokenKind::F64).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
326            let syms = e.state().0.syms;
327            TypeExpr::Named(Ident {
328                name: syms.f64,
329                span: span_from_extra(e),
330            })
331        });
332    let bool_parser =
333        just(TokenKind::Bool).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
334            let syms = e.state().0.syms;
335            TypeExpr::Named(Ident {
336                name: syms.bool,
337                span: span_from_extra(e),
338            })
339        });
340    let char_parser =
341        just(TokenKind::Char).map_with(|_, e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
342            let syms = e.state().0.syms;
343            TypeExpr::Named(Ident {
344                name: syms.char,
345                span: span_from_extra(e),
346            })
347        });
348
349    choice((
350        i8_parser.boxed(),
351        i16_parser.boxed(),
352        i32_parser.boxed(),
353        i64_parser.boxed(),
354        isize_parser.boxed(),
355        u8_parser.boxed(),
356        u16_parser.boxed(),
357        u32_parser.boxed(),
358        u64_parser.boxed(),
359        usize_parser.boxed(),
360        f16_parser.boxed(),
361        f32_parser.boxed(),
362        f64_parser.boxed(),
363        bool_parser.boxed(),
364        char_parser.boxed(),
365    ))
366    .boxed()
367}
368
369/// Parser for type expressions: primitive types (i32, bool, etc.), () for unit, ! for never, or [T; N] for arrays
370fn type_parser<'src, I>() -> GruelParser<'src, I, TypeExpr>
371where
372    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
373{
374    recursive(
375        move |ty: Recursive<Direct<'src, 'src, I, TypeExpr, ParserExtras<'src>>>| {
376            // Unit type: ()
377            let unit_type = just(TokenKind::LParen)
378                .then(just(TokenKind::RParen))
379                .map_with(|_, e| TypeExpr::Unit(span_from_extra(e)));
380
381            // Never type: !
382            let never_type =
383                just(TokenKind::Bang).map_with(|_, e| TypeExpr::Never(span_from_extra(e)));
384
385            // Array type: [T; N]
386            let array_type = just(TokenKind::LBracket)
387                .ignore_then(ty.clone())
388                .then_ignore(just(TokenKind::Semi))
389                .then(select! {
390                    TokenKind::Int(n) => n,
391                })
392                .then_ignore(just(TokenKind::RBracket))
393                .map_with(|(element, length), e| TypeExpr::Array {
394                    element: Box::new(element),
395                    length,
396                    span: span_from_extra(e),
397                });
398
399            // Anonymous struct type: struct { field: Type, ... }
400            // Used in comptime type construction
401            let anon_struct_field: GruelParser<'src, I, AnonStructField> = ident_parser()
402                .then_ignore(just(TokenKind::Colon))
403                .then(ty.clone())
404                .map_with(|(name, field_ty), e| AnonStructField {
405                    doc: None,
406                    name,
407                    ty: field_ty,
408                    span: span_from_extra(e),
409                })
410                .boxed();
411
412            let anon_struct_fields: GruelParser<'src, I, Vec<AnonStructField>> = anon_struct_field
413                .separated_by(just(TokenKind::Comma))
414                .allow_trailing()
415                .collect::<Vec<_>>()
416                .boxed();
417
418            let anon_struct_type = just(TokenKind::Struct)
419                .ignore_then(just(TokenKind::LBrace))
420                .ignore_then(anon_struct_fields)
421                .then_ignore(just(TokenKind::RBrace))
422                .map_with(|fields, e| TypeExpr::AnonymousStruct {
423                    directives: Directives::new(),
424                    posture: Posture::Affine,
425                    fields,
426                    methods: vec![],
427                    span: span_from_extra(e),
428                });
429
430            // Parameterized type call (ADR-0057): `Name(arg1, arg2, ...)`
431            // in type position. Resolves at sema time by evaluating the
432            // call as a comptime expression returning `type`.
433            let type_call = ident_parser()
434                .then(
435                    ty.clone()
436                        .separated_by(just(TokenKind::Comma))
437                        .allow_trailing()
438                        .at_least(1)
439                        .collect::<Vec<_>>()
440                        .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)),
441                )
442                .map_with(|(callee, args), e| TypeExpr::TypeCall {
443                    callee,
444                    args,
445                    span: span_from_extra(e),
446                });
447
448            // Named type: user-defined types like MyStruct
449            let named_type = ident_parser().map(TypeExpr::Named);
450
451            // Self type: Self keyword used in methods to refer to the containing struct
452            let self_type = just(TokenKind::SelfType).map_with(|_, e| {
453                let span = span_from_extra(e);
454                TypeExpr::Named(Ident {
455                    name: e.state().syms.self_type,
456                    span,
457                })
458            });
459
460            // Tuple type: (T,) for 1-tuples, (T, U) or (T, U,) for 2+-tuples.
461            // Must be tried after unit_type (which matches `()`) but before named_type.
462            // A 1-tuple requires a trailing comma to distinguish it from parenthesised
463            // types (not currently supported). We parse: LParen <ty> Comma (<ty>,)* RParen.
464            let tuple_type = just(TokenKind::LParen)
465                .ignore_then(ty.clone())
466                .then_ignore(just(TokenKind::Comma))
467                .then(
468                    ty.clone()
469                        .separated_by(just(TokenKind::Comma))
470                        .allow_trailing()
471                        .collect::<Vec<_>>(),
472                )
473                .then_ignore(just(TokenKind::RParen))
474                .map_with(|(first, rest), e| {
475                    let mut elems = Vec::with_capacity(1 + rest.len());
476                    elems.push(first);
477                    elems.extend(rest);
478                    TypeExpr::Tuple {
479                        elems,
480                        span: span_from_extra(e),
481                    }
482                });
483
484            choice((
485                unit_type.boxed(),
486                never_type.boxed(),
487                array_type.boxed(),
488                anon_struct_type.boxed(),
489                primitive_type_parser().boxed(),
490                self_type.boxed(),
491                tuple_type.boxed(),
492                // type_call must come before named_type — they share the
493                // ident prefix, but type_call requires the trailing
494                // `(...)` whereas named_type matches a bare ident.
495                type_call.boxed(),
496                named_type.boxed(),
497            ))
498            .boxed()
499        },
500    )
501    .boxed()
502}
503
504/// Parser for parameter mode: comptime. The legacy `inout` / `borrow`
505/// keyword modes were retired by ADR-0076 Phase 6 — the new sole form
506/// is `name: Ref(T)` / `name: MutRef(T)`.
507fn param_mode_parser<'src, I>() -> GruelParser<'src, I, ParamMode>
508where
509    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
510{
511    just(TokenKind::Comptime).to(ParamMode::Comptime).boxed()
512}
513
514/// Parser for function parameters: [comptime] [inout|borrow] name: type
515fn param_parser<'src, I>() -> GruelParser<'src, I, Param>
516where
517    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
518{
519    just(TokenKind::Comptime)
520        .or_not()
521        .then(param_mode_parser().or_not())
522        .then(ident_parser())
523        .then_ignore(just(TokenKind::Colon))
524        .then(type_parser())
525        .map_with(|(((is_comptime, mode), name), ty), e| Param {
526            is_comptime: is_comptime.is_some(),
527            mode: mode.unwrap_or(ParamMode::Normal),
528            name,
529            ty,
530            span: span_from_extra(e),
531        })
532        .boxed()
533}
534
535/// Parser for struct field declarations: [pub] name: type
536fn field_decl_parser<'src, I>() -> GruelParser<'src, I, FieldDecl>
537where
538    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
539{
540    // ADR-0073: optional `pub` prefix.
541    let visibility = just(TokenKind::Pub).or_not().map(|opt| {
542        if opt.is_some() {
543            Visibility::Public
544        } else {
545            Visibility::Private
546        }
547    });
548
549    visibility
550        .then(ident_parser())
551        .then_ignore(just(TokenKind::Colon))
552        .then(type_parser())
553        .map_with(|((visibility, name), ty), e| FieldDecl {
554            doc: None,
555            visibility,
556            name,
557            ty,
558            span: span_from_extra(e),
559        })
560        .boxed()
561}
562
563/// Parser for comma-separated struct field declarations
564fn field_decls_parser<'src, I>() -> GruelParser<'src, I, Vec<FieldDecl>>
565where
566    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
567{
568    field_decl_parser()
569        .separated_by(just(TokenKind::Comma))
570        .allow_trailing()
571        .collect::<Vec<_>>()
572        .boxed()
573}
574
575/// Parser for comma-separated parameters
576fn params_parser<'src, I>() -> GruelParser<'src, I, Vec<Param>>
577where
578    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
579{
580    param_parser()
581        .separated_by(just(TokenKind::Comma))
582        .collect::<Vec<_>>()
583        .boxed()
584}
585
586/// Parser for a single directive: @name or @name(arg1, arg2, ...)
587///
588/// `name` accepts both bare identifiers and the `derive` reserved keyword
589/// (ADR-0058) so that `@derive(Foo)` works even though `derive` is also a
590/// keyword for top-level derive items.
591fn directive_parser<'src, I>() -> GruelParser<'src, I, Directive>
592where
593    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
594{
595    let directive_name = choice((
596        ident_parser().boxed(),
597        select! {
598            TokenKind::Derive = e => {
599                let state: &mut SimpleState<ParserState> = e.state();
600                Ident {
601                    name: state.0.syms.derive_name,
602                    span: span_from_extra(e),
603                }
604            },
605        }
606        .boxed(),
607    ))
608    .boxed();
609
610    // ADR-0083: `@mark(...)` directive args. After Phase 4 retired
611    // `TokenKind::Linear`, both `copy` and `linear` are regular
612    // identifiers and fall through to `ident_parser`. ADR-0088
613    // Phase 6 retired `TokenKind::Unchecked` similarly.
614    let directive_arg = choice((
615        select! {
616            TokenKind::String(s) = e => DirectiveArg::String(StringLit {
617                value: s,
618                span: span_from_extra(e),
619            }),
620        }
621        .boxed(),
622        ident_parser().map(DirectiveArg::Ident).boxed(),
623    ))
624    .boxed();
625
626    just(TokenKind::At)
627        .ignore_then(directive_name)
628        .then(
629            directive_arg
630                .separated_by(just(TokenKind::Comma))
631                .allow_trailing()
632                .collect::<Vec<_>>()
633                .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen))
634                .or_not(),
635        )
636        .map_with(|(name, args), e| Directive {
637            name,
638            args: args.unwrap_or_default(),
639            span: span_from_extra(e),
640        })
641        .boxed()
642}
643
644/// Parser for zero or more directives
645fn directives_parser<'src, I>() -> GruelParser<'src, I, Directives>
646where
647    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
648{
649    // Chumsky's collect() requires its Container trait, which SmallVec doesn't implement.
650    // So we collect to Vec first, then convert. The overhead is minimal since most
651    // items have 0-1 directives (Vec is cheap for empty/small collections).
652    directive_parser()
653        .repeated()
654        .collect::<Vec<_>>()
655        .map(|v| v.into_iter().collect())
656        .boxed()
657}
658
659/// Parser for a single call argument. The legacy `inout` / `borrow`
660/// keyword arg modes were retired by ADR-0076 Phase 6 — the new sole
661/// form is `&expr` / `&mut expr`.
662fn call_arg_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, CallArg>
663where
664    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
665{
666    chumsky::primitive::empty()
667        .map(|_| None::<ArgMode>)
668        .then(expr)
669        .map_with(|(mode, expr), e| CallArg {
670            mode: mode.unwrap_or(ArgMode::Normal),
671            expr,
672            span: span_from_extra(e),
673        })
674        .boxed()
675}
676
677/// Parser for comma-separated call arguments with optional inout
678fn call_args_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, Vec<CallArg>>
679where
680    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
681{
682    call_arg_parser(expr)
683        .separated_by(just(TokenKind::Comma))
684        .collect::<Vec<_>>()
685        .boxed()
686}
687
688/// Parser for comma-separated expression arguments (for contexts that don't support inout)
689fn args_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, Vec<Expr>>
690where
691    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
692{
693    expr.separated_by(just(TokenKind::Comma))
694        .collect::<Vec<_>>()
695        .boxed()
696}
697
698/// Parser for struct field initializers: name: expr
699fn field_init_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, FieldInit>
700where
701    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
702{
703    // Field init with explicit value: `name: expr`
704    let explicit = ident_parser()
705        .then_ignore(just(TokenKind::Colon))
706        .then(expr)
707        .map_with(|(name, value), e| FieldInit {
708            name,
709            value: Box::new(value),
710            span: span_from_extra(e),
711        });
712
713    // Field init shorthand: `name` means `name: name`
714    let shorthand = ident_parser().map_with(|name, e| FieldInit {
715        value: Box::new(Expr::Ident(name)),
716        name,
717        span: span_from_extra(e),
718    });
719
720    choice((explicit, shorthand)).boxed()
721}
722
723/// Parser for comma-separated field initializers
724fn field_inits_parser<'src, I>(
725    expr: GruelParser<'src, I, Expr>,
726) -> GruelParser<'src, I, Vec<FieldInit>>
727where
728    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
729{
730    field_init_parser(expr)
731        .separated_by(just(TokenKind::Comma))
732        .allow_trailing()
733        .collect::<Vec<_>>()
734        .boxed()
735}
736
737/// Helper to create binary expression
738fn make_binary(left: Expr, op: BinaryOp, right: Expr) -> Expr {
739    let span = Span::new(left.span().start, right.span().end);
740    Expr::Binary(BinaryExpr {
741        left: Box::new(left),
742        op,
743        right: Box::new(right),
744        span,
745    })
746}
747
748/// Helper to create unary expression
749fn make_unary(op: UnaryOp, operand: Expr, op_span: SimpleSpan) -> Expr {
750    let span = Span::new(offset_to_u32(op_span.start), operand.span().end);
751    Expr::Unary(UnaryExpr {
752        op,
753        operand: Box::new(operand),
754        span,
755    })
756}
757
758/// Expression parser using manual precedence climbing.
759///
760/// Operator precedence (tightest binding first):
761///   unary prefix: -, !, ~
762///   multiplicative: *, /, %
763///   additive: +, -
764///   shift: <<, >>
765///   comparison: ==, !=, <, >, <=, >=
766///   bitwise AND: &
767///   bitwise XOR: ^
768///   bitwise OR: |
769///   logical AND: &&
770///   logical OR: ||  (loosest binding)
771///
772/// Each level is immediately `.boxed()` to keep the Rc'd type short and avoid
773/// the huge drop-glue symbols that Chumsky's Pratt parser would generate (the
774/// Pratt operator-tuple type embeds `I` dozens of times).
775fn expr_parser<'src, I>() -> GruelParser<'src, I, Expr>
776where
777    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
778{
779    recursive(
780        |expr: Recursive<Direct<'src, 'src, I, Expr, ParserExtras<'src>>>| {
781            // Atom parser – primary expressions (highest precedence).
782            let atom: GruelParser<I, Expr> = atom_parser(expr.clone().boxed());
783
784            // Unary prefix operators: -, !, ~  (right-associative; stack and apply inside-out)
785            let unary: GruelParser<I, Expr> = {
786                let prefix_op: GruelParser<I, (UnaryOp, SimpleSpan)> = choice((
787                    just(TokenKind::Minus)
788                        .map_with(|_, e| (UnaryOp::Neg, e.span()))
789                        .boxed(),
790                    just(TokenKind::Bang)
791                        .map_with(|_, e| (UnaryOp::Not, e.span()))
792                        .boxed(),
793                    just(TokenKind::Tilde)
794                        .map_with(|_, e| (UnaryOp::BitNot, e.span()))
795                        .boxed(),
796                    // ADR-0062: `&mut x` constructs a `MutRef(T)` (must come
797                    // before the bare `&` so we don't fall through to `Ref`).
798                    just(TokenKind::Amp)
799                        .then(just(TokenKind::Mut))
800                        .map_with(|_, e| (UnaryOp::MutRef, e.span()))
801                        .boxed(),
802                    // ADR-0062: `&x` constructs a `Ref(T)`.
803                    just(TokenKind::Amp)
804                        .map_with(|_, e| (UnaryOp::Ref, e.span()))
805                        .boxed(),
806                ))
807                .boxed();
808                prefix_op
809                    .repeated()
810                    .collect::<Vec<_>>()
811                    .then(atom.clone())
812                    .map(|(mut ops, mut rhs)| {
813                        // Apply operators from innermost (rightmost) outward.
814                        ops.reverse();
815                        for (op, span) in ops {
816                            rhs = make_unary(op, rhs, span);
817                        }
818                        rhs
819                    })
820                    .boxed()
821            };
822
823            // Helper macro: build one left-associative binary level.
824            // Each call boxes the result, keeping the drop-glue type short.
825            macro_rules! left_binary {
826                ($prev:expr, $op_parser:expr) => {{
827                    let prev: GruelParser<I, Expr> = $prev;
828                    let op: GruelParser<I, BinaryOp> = $op_parser;
829                    prev.clone()
830                        .foldl(op.then(prev).repeated(), |l, (op, r)| make_binary(l, op, r))
831                        .boxed()
832                }};
833            }
834
835            // Multiplicative: *, /, %
836            let multiplicative: GruelParser<I, Expr> = left_binary!(
837                unary,
838                choice((
839                    just(TokenKind::Star).to(BinaryOp::Mul).boxed(),
840                    just(TokenKind::Slash).to(BinaryOp::Div).boxed(),
841                    just(TokenKind::Percent).to(BinaryOp::Mod).boxed(),
842                ))
843                .boxed()
844            );
845
846            // Additive: +, -
847            let additive: GruelParser<I, Expr> = left_binary!(
848                multiplicative,
849                choice((
850                    just(TokenKind::Plus).to(BinaryOp::Add).boxed(),
851                    just(TokenKind::Minus).to(BinaryOp::Sub).boxed(),
852                ))
853                .boxed()
854            );
855
856            // Shift: <<, >>
857            let shift: GruelParser<I, Expr> = left_binary!(
858                additive,
859                choice((
860                    just(TokenKind::LtLt).to(BinaryOp::Shl).boxed(),
861                    just(TokenKind::GtGt).to(BinaryOp::Shr).boxed(),
862                ))
863                .boxed()
864            );
865
866            // Comparison: ==, !=, <, >, <=, >=
867            let comparison: GruelParser<I, Expr> = left_binary!(
868                shift,
869                choice((
870                    just(TokenKind::EqEq).to(BinaryOp::Eq).boxed(),
871                    just(TokenKind::BangEq).to(BinaryOp::Ne).boxed(),
872                    just(TokenKind::Lt).to(BinaryOp::Lt).boxed(),
873                    just(TokenKind::Gt).to(BinaryOp::Gt).boxed(),
874                    just(TokenKind::LtEq).to(BinaryOp::Le).boxed(),
875                    just(TokenKind::GtEq).to(BinaryOp::Ge).boxed(),
876                ))
877                .boxed()
878            );
879
880            // Bitwise AND: &
881            let bitwise_and: GruelParser<I, Expr> = left_binary!(
882                comparison,
883                just(TokenKind::Amp).to(BinaryOp::BitAnd).boxed()
884            );
885
886            // Bitwise XOR: ^
887            let bitwise_xor: GruelParser<I, Expr> = left_binary!(
888                bitwise_and,
889                just(TokenKind::Caret).to(BinaryOp::BitXor).boxed()
890            );
891
892            // Bitwise OR: |
893            let bitwise_or: GruelParser<I, Expr> = left_binary!(
894                bitwise_xor,
895                just(TokenKind::Pipe).to(BinaryOp::BitOr).boxed()
896            );
897
898            // Logical AND: &&
899            let logical_and: GruelParser<I, Expr> = left_binary!(
900                bitwise_or,
901                just(TokenKind::AmpAmp).to(BinaryOp::And).boxed()
902            );
903
904            // Logical OR: || (lowest binary precedence)
905            let logical_or: GruelParser<I, Expr> = left_binary!(
906                logical_and,
907                just(TokenKind::PipePipe).to(BinaryOp::Or).boxed()
908            );
909
910            logical_or
911        },
912    )
913    .boxed()
914}
915
916/// Parser for patterns in match arms
917fn pattern_parser<'src, I>() -> GruelParser<'src, I, Pattern>
918where
919    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
920{
921    recursive(
922        |pat: Recursive<Direct<'src, 'src, _, Pattern, ParserExtras<'src>>>| {
923            // Wildcard pattern: _
924            let wildcard =
925                just(TokenKind::Underscore).map_with(|_, e| Pattern::Wildcard(span_from_extra(e)));
926
927            // Integer literal pattern (positive or zero)
928            let int_pat = select! {
929                TokenKind::Int(n) = e => Pattern::Int(IntLit {
930                    value: n,
931                    span: span_from_extra(e),
932                }),
933            };
934
935            // Negative integer literal pattern: - followed by integer
936            let neg_int_pat = just(TokenKind::Minus)
937                .then(select! { TokenKind::Int(n) => n })
938                .map_with(|(_, n), e| {
939                    Pattern::NegInt(NegIntLit {
940                        value: n,
941                        span: span_from_extra(e),
942                    })
943                });
944
945            // Boolean literal patterns
946            let bool_true = select! {
947                TokenKind::True = e => Pattern::Bool(BoolLit {
948                    value: true,
949                    span: span_from_extra(e),
950                }),
951            };
952
953            let bool_false = select! {
954                TokenKind::False = e => Pattern::Bool(BoolLit {
955                    value: false,
956                    span: span_from_extra(e),
957                }),
958            };
959
960            // A rest pattern `..`: two adjacent Dot tokens (the lexer has no DotDot).
961            // `..` is only legal inside a tuple/struct/variant sequence. ADR-0049 Phase 6.
962            let rest_token = just(TokenKind::Dot)
963                .then(just(TokenKind::Dot))
964                .map_with(|_, e| span_from_extra(e));
965
966            // A leaf binding in a pattern sub-position: `_`, `x`, or `mut x`. These can
967            // still appear anywhere a full sub-pattern is legal; full sub-patterns go
968            // through `pat` (the recursive reference).
969            let leaf_binding = choice((
970                just(TokenKind::Underscore).map_with(|_, e| Pattern::Wildcard(span_from_extra(e))),
971                just(TokenKind::Mut)
972                    .ignore_then(ident_parser())
973                    .map_with(|name, e| Pattern::Ident {
974                        is_mut: true,
975                        name,
976                        span: span_from_extra(e),
977                    }),
978                ident_parser().map_with(|name, e| Pattern::Ident {
979                    is_mut: false,
980                    name,
981                    span: span_from_extra(e),
982                }),
983            ));
984
985            // Sub-pattern: any full pattern OR a leaf binding (as a shortcut). In match
986            // contexts we need `mut x` at the sub-pattern position, which the recursive
987            // `pat` doesn't emit (only full patterns do).
988            //
989            // Order: try the recursive pattern first (for nested Enum::V / Struct { .. } /
990            // (.., ..) shapes), then fall back to the leaf binding for bare idents,
991            // wildcards, and `mut x`. A bare ident matches both as a Pattern::Ident inside
992            // `pat` and as a leaf binding — either path yields the same AST.
993            let sub_pattern = choice((pat.clone(), leaf_binding.clone())).boxed();
994
995            // One element in a tuple / variant-tuple sequence: either `..` or a sub-pattern.
996            let tuple_elem = choice((
997                rest_token.map(TupleElemPattern::Rest),
998                sub_pattern.clone().map(TupleElemPattern::Pattern),
999            ))
1000            .boxed();
1001
1002            // `(e1, e2, ...)` tuple-like sequence body.
1003            let tuple_suffix = tuple_elem
1004                .clone()
1005                .separated_by(just(TokenKind::Comma))
1006                .allow_trailing()
1007                .collect::<Vec<_>>()
1008                .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen));
1009
1010            // Named field in a struct / struct-variant pattern: `field`, `field: sub`,
1011            // `mut field`, `mut field: sub`, or the rest sentinel `..`.
1012            let field_rest = rest_token.map_with(|span, _e| FieldPattern {
1013                field_name: None,
1014                sub: None,
1015                is_mut: false,
1016                span,
1017            });
1018            let field_explicit = just(TokenKind::Mut)
1019                .or_not()
1020                .then(ident_parser())
1021                .then_ignore(just(TokenKind::Colon))
1022                .then(sub_pattern.clone())
1023                .map_with(|((is_mut, field_name), sub), e| FieldPattern {
1024                    field_name: Some(field_name),
1025                    sub: Some(sub),
1026                    is_mut: is_mut.is_some(),
1027                    span: span_from_extra(e),
1028                });
1029            let field_shorthand =
1030                just(TokenKind::Mut)
1031                    .or_not()
1032                    .then(ident_parser())
1033                    .map_with(|(is_mut, name), e| FieldPattern {
1034                        field_name: Some(name),
1035                        sub: None,
1036                        is_mut: is_mut.is_some(),
1037                        span: span_from_extra(e),
1038                    });
1039            let field_pat = choice((field_rest, field_explicit, field_shorthand)).boxed();
1040
1041            let struct_suffix = field_pat
1042                .clone()
1043                .separated_by(just(TokenKind::Comma))
1044                .allow_trailing()
1045                .collect::<Vec<_>>()
1046                .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace));
1047
1048            // Enum for the suffix on an enum variant pattern: tuple, struct, or none.
1049            #[derive(Debug, Clone)]
1050            enum PatternSuffix {
1051                Tuple(Vec<TupleElemPattern>),
1052                Struct(Vec<FieldPattern>),
1053            }
1054
1055            let pattern_suffix = choice((
1056                tuple_suffix.clone().map(PatternSuffix::Tuple),
1057                struct_suffix.clone().map(PatternSuffix::Struct),
1058            ));
1059
1060            // Self path pattern: Self::Variant, Self::Variant(sub, ...), or Self::Variant { field: sub, ... }
1061            let self_path_pat = just(TokenKind::SelfType)
1062                .ignore_then(just(TokenKind::ColonColon))
1063                .ignore_then(ident_parser())
1064                .then(pattern_suffix.clone().or_not())
1065                .map_with(|(variant, suffix_opt), e| {
1066                    let span = span_from_extra(e);
1067                    let type_name = Ident {
1068                        name: e.state().syms.self_type,
1069                        span,
1070                    };
1071                    match suffix_opt {
1072                        Some(PatternSuffix::Tuple(fields)) => Pattern::DataVariant {
1073                            base: None,
1074                            type_name,
1075                            variant,
1076                            fields,
1077                            span,
1078                        },
1079                        Some(PatternSuffix::Struct(fields)) => Pattern::StructVariant {
1080                            base: None,
1081                            type_name,
1082                            variant,
1083                            fields,
1084                            span,
1085                        },
1086                        None => Pattern::Path(PathPattern {
1087                            base: None,
1088                            type_name,
1089                            variant,
1090                            span,
1091                        }),
1092                    }
1093                });
1094
1095            // IDENT (followed by `{`, `::`, or nothing): either a struct destructure,
1096            // a unit/data/struct variant, or a bare ident binding.
1097            //
1098            // `IDENT { ... }` → Pattern::Struct
1099            // `IDENT::IDENT[(...)]` / `IDENT::IDENT{ ... }` → Pattern::{Path, DataVariant, StructVariant}
1100            let struct_destructure =
1101                ident_parser()
1102                    .then(struct_suffix.clone())
1103                    .map_with(|(type_name, fields), e| Pattern::Struct {
1104                        type_name,
1105                        fields,
1106                        span: span_from_extra(e),
1107                    });
1108
1109            let simple_path_pat = ident_parser()
1110                .then_ignore(just(TokenKind::ColonColon))
1111                .then(ident_parser())
1112                .then(pattern_suffix.clone().or_not())
1113                .map_with(|((type_name, variant), suffix_opt), e| match suffix_opt {
1114                    Some(PatternSuffix::Tuple(fields)) => Pattern::DataVariant {
1115                        base: None,
1116                        type_name,
1117                        variant,
1118                        fields,
1119                        span: span_from_extra(e),
1120                    },
1121                    Some(PatternSuffix::Struct(fields)) => Pattern::StructVariant {
1122                        base: None,
1123                        type_name,
1124                        variant,
1125                        fields,
1126                        span: span_from_extra(e),
1127                    },
1128                    None => Pattern::Path(PathPattern {
1129                        base: None,
1130                        type_name,
1131                        variant,
1132                        span: span_from_extra(e),
1133                    }),
1134                });
1135
1136            // Qualified path pattern: module.Enum::Variant or module.sub.Enum::Variant
1137            let qualified_path_pat = ident_parser()
1138                .then(
1139                    just(TokenKind::Dot)
1140                        .ignore_then(ident_parser())
1141                        .repeated()
1142                        .at_least(1)
1143                        .collect::<Vec<_>>(),
1144                )
1145                .then_ignore(just(TokenKind::ColonColon))
1146                .then(ident_parser())
1147                .then(pattern_suffix.or_not())
1148                .map_with(|(((first, mut rest), variant), suffix_opt), e| {
1149                    let type_name = rest.pop().expect("at_least(1) guarantees non-empty");
1150
1151                    let base_expr = if rest.is_empty() {
1152                        Expr::Ident(first)
1153                    } else {
1154                        let mut base = Expr::Ident(first);
1155                        for field in rest {
1156                            let span = base.span().extend_to(field.span.end);
1157                            base = Expr::Field(FieldExpr {
1158                                base: Box::new(base),
1159                                field,
1160                                span,
1161                            });
1162                        }
1163                        base
1164                    };
1165
1166                    match suffix_opt {
1167                        Some(PatternSuffix::Tuple(fields)) => Pattern::DataVariant {
1168                            base: Some(Box::new(base_expr)),
1169                            type_name,
1170                            variant,
1171                            fields,
1172                            span: span_from_extra(e),
1173                        },
1174                        Some(PatternSuffix::Struct(fields)) => Pattern::StructVariant {
1175                            base: Some(Box::new(base_expr)),
1176                            type_name,
1177                            variant,
1178                            fields,
1179                            span: span_from_extra(e),
1180                        },
1181                        None => Pattern::Path(PathPattern {
1182                            base: Some(Box::new(base_expr)),
1183                            type_name,
1184                            variant,
1185                            span: span_from_extra(e),
1186                        }),
1187                    }
1188                });
1189
1190            // Tuple pattern (match context or nested position): `(p1, p2, ...)` with at
1191            // least one comma, or `(p,)` for a 1-tuple. `(p)` remains a parenthesised
1192            // pattern — not supported here (redundant: just write `p`).
1193            let tuple_pat = just(TokenKind::LParen)
1194                .ignore_then(tuple_elem.clone())
1195                .then_ignore(just(TokenKind::Comma))
1196                .then(
1197                    tuple_elem
1198                        .clone()
1199                        .separated_by(just(TokenKind::Comma))
1200                        .allow_trailing()
1201                        .collect::<Vec<_>>(),
1202                )
1203                .then_ignore(just(TokenKind::RParen))
1204                .map_with(|(first, rest), e| {
1205                    let mut elems = Vec::with_capacity(1 + rest.len());
1206                    elems.push(first);
1207                    elems.extend(rest);
1208                    Pattern::Tuple {
1209                        elems,
1210                        span: span_from_extra(e),
1211                    }
1212                });
1213
1214            // Plain ident / mut-ident leaf as a top-level pattern (bare binding).
1215            let ident_leaf =
1216                just(TokenKind::Mut)
1217                    .or_not()
1218                    .then(ident_parser())
1219                    .map_with(|(is_mut, name), e| Pattern::Ident {
1220                        is_mut: is_mut.is_some(),
1221                        name,
1222                        span: span_from_extra(e),
1223                    });
1224
1225            choice((
1226                wildcard.boxed(),
1227                neg_int_pat.boxed(),
1228                int_pat.boxed(),
1229                bool_true.boxed(),
1230                bool_false.boxed(),
1231                tuple_pat.boxed(),
1232                // Try qualified path first (has more structure), then Self path, then
1233                // simple path, then struct destructure, then bare ident. Struct destructure
1234                // and simple path both start with IDENT; `IDENT::` disambiguates. Struct
1235                // destructure requires `IDENT {`, a combination that doesn't conflict with
1236                // the bare ident parser which is tried last.
1237                qualified_path_pat.boxed(),
1238                self_path_pat.boxed(),
1239                simple_path_pat.boxed(),
1240                struct_destructure.boxed(),
1241                ident_leaf.boxed(),
1242            ))
1243            .boxed()
1244        },
1245    )
1246    .boxed()
1247}
1248
1249/// Parser for a single match arm: pattern => expr  OR
1250/// `comptime_unroll for ident in expr { body }` (ADR-0079 Phase 3),
1251/// which the compiler expands at sema time into one regular arm
1252/// per element of the iterable.
1253fn match_arm_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, MatchArm>
1254where
1255    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
1256{
1257    let regular_arm = pattern_parser()
1258        .then_ignore(just(TokenKind::FatArrow))
1259        .then(expr.clone())
1260        .map_with(|(pattern, body), e| MatchArm {
1261            pattern,
1262            body: Box::new(body),
1263            span: span_from_extra(e),
1264        });
1265
1266    let unroll_arm = just(TokenKind::ComptimeUnroll)
1267        .ignore_then(just(TokenKind::For))
1268        .ignore_then(ident_parser())
1269        .then_ignore(just(TokenKind::In))
1270        .then(expr.clone())
1271        .then(maybe_unit_block_parser(expr))
1272        .map_with(|((binding, iterable), body), e| {
1273            let span = span_from_extra(e);
1274            MatchArm {
1275                pattern: Pattern::ComptimeUnrollArm {
1276                    binding,
1277                    iterable: Box::new(iterable),
1278                    span,
1279                },
1280                body: Box::new(Expr::Block(body)),
1281                span,
1282            }
1283        });
1284
1285    choice((unroll_arm, regular_arm)).boxed()
1286}
1287
1288/// Parser for literal expressions: integers, strings, booleans, and unit
1289fn literal_parser<'src, I>() -> GruelParser<'src, I, Expr>
1290where
1291    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
1292{
1293    // Integer literal
1294    let int_lit = select! {
1295        TokenKind::Int(n) = e => Expr::Int(IntLit {
1296            value: n,
1297            span: span_from_extra(e),
1298        }),
1299    };
1300
1301    // Floating-point literal
1302    let float_lit = select! {
1303        TokenKind::Float(bits) = e => Expr::Float(FloatLit {
1304            bits,
1305            span: span_from_extra(e),
1306        }),
1307    };
1308
1309    // String literal
1310    let string_lit = select! {
1311        TokenKind::String(s) = e => Expr::String(StringLit {
1312            value: s,
1313            span: span_from_extra(e),
1314        }),
1315    };
1316
1317    // Char literal (ADR-0071)
1318    let char_lit = select! {
1319        TokenKind::CharLit(c) = e => Expr::Char(CharLit {
1320            value: c,
1321            span: span_from_extra(e),
1322        }),
1323    };
1324
1325    // Boolean literals
1326    let bool_true = select! {
1327        TokenKind::True = e => Expr::Bool(BoolLit {
1328            value: true,
1329            span: span_from_extra(e),
1330        }),
1331    };
1332
1333    let bool_false = select! {
1334        TokenKind::False = e => Expr::Bool(BoolLit {
1335            value: false,
1336            span: span_from_extra(e),
1337        }),
1338    };
1339
1340    // Unit literal: ()
1341    let unit_lit = just(TokenKind::LParen)
1342        .then(just(TokenKind::RParen))
1343        .map_with(|_, e| {
1344            Expr::Unit(UnitLit {
1345                span: span_from_extra(e),
1346            })
1347        });
1348
1349    choice((
1350        int_lit.boxed(),
1351        float_lit.boxed(),
1352        string_lit.boxed(),
1353        char_lit.boxed(),
1354        bool_true.boxed(),
1355        bool_false.boxed(),
1356        unit_lit.boxed(),
1357    ))
1358    .boxed()
1359}
1360
1361/// Parser for control flow expressions: break, continue, return, if, while, loop, match
1362fn control_flow_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, Expr>
1363where
1364    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
1365{
1366    // Break
1367    let break_expr = select! {
1368        TokenKind::Break = e => Expr::Break(BreakExpr { span: span_from_extra(e) }),
1369    };
1370
1371    // Continue
1372    let continue_expr = select! {
1373        TokenKind::Continue = e => Expr::Continue(ContinueExpr { span: span_from_extra(e) }),
1374    };
1375
1376    // Return expression: return <expr>? (expression is optional for unit-returning functions)
1377    let return_expr = just(TokenKind::Return)
1378        .ignore_then(expr.clone().or_not())
1379        .map_with(|value, e| {
1380            Expr::Return(ReturnExpr {
1381                value: value.map(Box::new),
1382                span: span_from_extra(e),
1383            })
1384        });
1385
1386    // If expression - defined with recursive reference to allow `else if` chains.
1387    // ADR-0079 follow-up: an optional leading `comptime` keyword marks the
1388    // branch as a comptime-evaluated dispatch (see `IfExpr::is_comptime`).
1389    let if_expr: GruelParser<'src, I, Expr> = recursive(
1390        |if_expr_rec: Recursive<Direct<'src, 'src, I, Expr, ParserExtras<'src>>>| {
1391            just(TokenKind::Comptime)
1392                .or_not()
1393                .then_ignore(just(TokenKind::If))
1394                .then(expr.clone())
1395                .then(maybe_unit_block_parser(expr.clone()))
1396                .then(
1397                    just(TokenKind::Else)
1398                        .ignore_then(choice((
1399                            // else if: wrap the nested if in a synthetic block
1400                            if_expr_rec
1401                                .map_with(|nested_if, e| {
1402                                    let span = span_from_extra(e);
1403                                    BlockExpr {
1404                                        statements: Vec::new(),
1405                                        expr: Box::new(nested_if),
1406                                        span,
1407                                    }
1408                                })
1409                                .boxed(),
1410                            // else { ... }: parse a regular block
1411                            maybe_unit_block_parser(expr.clone()),
1412                        )))
1413                        .or_not(),
1414                )
1415                .map_with(|(((comptime_kw, cond), then_block), else_block), e| {
1416                    Expr::If(IfExpr {
1417                        cond: Box::new(cond),
1418                        then_block,
1419                        else_block,
1420                        span: span_from_extra(e),
1421                        is_comptime: comptime_kw.is_some(),
1422                    })
1423                })
1424                .boxed()
1425        },
1426    )
1427    .boxed();
1428
1429    // While expression
1430    let while_expr: GruelParser<'src, I, Expr> = just(TokenKind::While)
1431        .ignore_then(expr.clone())
1432        .then(maybe_unit_block_parser(expr.clone()))
1433        .map_with(|(cond, body), e| {
1434            Expr::While(WhileExpr {
1435                cond: Box::new(cond),
1436                body,
1437                span: span_from_extra(e),
1438            })
1439        })
1440        .boxed();
1441
1442    // For-in expression: for [mut] ident in expr { body }
1443    let for_expr: GruelParser<'src, I, Expr> = just(TokenKind::For)
1444        .ignore_then(just(TokenKind::Mut).or_not())
1445        .then(ident_parser())
1446        .then_ignore(just(TokenKind::In))
1447        .then(expr.clone())
1448        .then(maybe_unit_block_parser(expr.clone()))
1449        .map_with(|(((is_mut, binding), iterable), body), e| {
1450            Expr::For(ForExpr {
1451                binding,
1452                is_mut: is_mut.is_some(),
1453                iterable: Box::new(iterable),
1454                body,
1455                span: span_from_extra(e),
1456            })
1457        })
1458        .boxed();
1459
1460    // Loop expression (infinite loop)
1461    let loop_expr: GruelParser<'src, I, Expr> = just(TokenKind::Loop)
1462        .ignore_then(maybe_unit_block_parser(expr.clone()))
1463        .map_with(|body, e| {
1464            Expr::Loop(LoopExpr {
1465                body,
1466                span: span_from_extra(e),
1467            })
1468        })
1469        .boxed();
1470
1471    // Match expression: match scrutinee { pattern => expr, ... }
1472    let match_expr: GruelParser<'src, I, Expr> = just(TokenKind::Match)
1473        .ignore_then(expr.clone())
1474        .then(
1475            match_arm_parser(expr.clone())
1476                .separated_by(just(TokenKind::Comma))
1477                .allow_trailing()
1478                .collect::<Vec<_>>()
1479                .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)),
1480        )
1481        .map_with(|(scrutinee, arms), e| {
1482            Expr::Match(MatchExpr {
1483                scrutinee: Box::new(scrutinee),
1484                arms,
1485                span: span_from_extra(e),
1486            })
1487        })
1488        .boxed();
1489
1490    // Comptime unroll for expression: comptime_unroll for ident in expr { body }
1491    let comptime_unroll_for_expr: GruelParser<'src, I, Expr> = just(TokenKind::ComptimeUnroll)
1492        .ignore_then(just(TokenKind::For))
1493        .ignore_then(ident_parser())
1494        .then_ignore(just(TokenKind::In))
1495        .then(expr.clone())
1496        .then(maybe_unit_block_parser(expr.clone()))
1497        .map_with(|((binding, iterable), body), e| {
1498            Expr::ComptimeUnrollFor(ComptimeUnrollForExpr {
1499                binding,
1500                iterable: Box::new(iterable),
1501                body,
1502                span: span_from_extra(e),
1503            })
1504        })
1505        .boxed();
1506
1507    choice((
1508        break_expr.boxed(),
1509        continue_expr.boxed(),
1510        return_expr.boxed(),
1511        if_expr,
1512        while_expr,
1513        for_expr,
1514        comptime_unroll_for_expr,
1515        loop_expr,
1516        match_expr,
1517    ))
1518    .boxed()
1519}
1520
1521/// What can follow an identifier: call args, struct fields, path (::variant), path call (::fn()), or nothing
1522#[derive(Clone)]
1523enum IdentSuffix {
1524    Call(Vec<CallArg>),
1525    StructLit(Vec<FieldInit>),
1526    Path(Ident),                          // ::Variant (for enum variants)
1527    PathCall(Ident, Vec<CallArg>),        // ::function() (for associated functions)
1528    PathStructLit(Ident, Vec<FieldInit>), // ::Variant { field: value } (for enum struct variants)
1529    /// ADR-0063: `(type_args)::function(call_args)` — a type-call followed by a path call.
1530    TypeCallPathCall(Vec<Expr>, Ident, Vec<CallArg>),
1531    None,
1532}
1533
1534/// Parser for identifier-based expressions: identifiers, function calls, struct literals, and paths
1535fn call_and_access_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, Expr>
1536where
1537    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
1538{
1539    ident_parser()
1540        .then(
1541            choice((
1542                // ADR-0063: type-call path call `Name(type_args)::method(args)`.
1543                // Tried before plain `(args)` so it has a chance to match the
1544                // `::method(args)` tail; chumsky backtracks if that tail
1545                // isn't there.
1546                {
1547                    let type_args = expr
1548                        .clone()
1549                        .separated_by(just(TokenKind::Comma))
1550                        .allow_trailing()
1551                        .at_least(1)
1552                        .collect::<Vec<_>>()
1553                        .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen));
1554                    type_args
1555                        .then_ignore(just(TokenKind::ColonColon))
1556                        .then(ident_parser())
1557                        .then(
1558                            call_args_parser(expr.clone())
1559                                .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)),
1560                        )
1561                        .map(|((type_args, func), args)| {
1562                            IdentSuffix::TypeCallPathCall(type_args, func, args)
1563                        })
1564                        .boxed()
1565                },
1566                // Function call: (args)
1567                call_args_parser(expr.clone())
1568                    .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen))
1569                    .map(IdentSuffix::Call)
1570                    .boxed(),
1571                // Struct literal: { field: value, ... } or { field, ... } (shorthand)
1572                // Lookahead: require `{ }` or `{ ident :` or `{ ident ,`
1573                // to disambiguate from blocks like `if cond { expr }`
1574                just(TokenKind::LBrace)
1575                    .then(
1576                        choice((
1577                            just(TokenKind::RBrace).ignored(),
1578                            select! { TokenKind::Ident(_) => () }
1579                                .then_ignore(just(TokenKind::Colon))
1580                                .ignored(),
1581                            select! { TokenKind::Ident(_) => () }
1582                                .then_ignore(just(TokenKind::Comma))
1583                                .ignored(),
1584                        ))
1585                        .rewind(),
1586                    )
1587                    .rewind()
1588                    .ignore_then(
1589                        field_inits_parser(expr.clone())
1590                            .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)),
1591                    )
1592                    .map(IdentSuffix::StructLit)
1593                    .boxed(),
1594                // Associated function call: ::function(args)
1595                just(TokenKind::ColonColon)
1596                    .ignore_then(ident_parser())
1597                    .then(
1598                        call_args_parser(expr.clone())
1599                            .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)),
1600                    )
1601                    .map(|(func, args)| IdentSuffix::PathCall(func, args))
1602                    .boxed(),
1603                // Enum struct variant literal: ::Variant { field: value, ... }
1604                just(TokenKind::ColonColon)
1605                    .ignore_then(ident_parser())
1606                    .then(
1607                        field_inits_parser(expr.clone())
1608                            .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)),
1609                    )
1610                    .map(|(variant, fields)| IdentSuffix::PathStructLit(variant, fields))
1611                    .boxed(),
1612                // Path: ::Variant (for enum variants)
1613                just(TokenKind::ColonColon)
1614                    .ignore_then(ident_parser())
1615                    .map(IdentSuffix::Path)
1616                    .boxed(),
1617            ))
1618            .or_not()
1619            .map(|opt| opt.unwrap_or(IdentSuffix::None)),
1620        )
1621        .map_with(|(name, suffix), e| match suffix {
1622            IdentSuffix::Call(args) => Expr::Call(CallExpr {
1623                name,
1624                args,
1625                span: span_from_extra(e),
1626            }),
1627            IdentSuffix::StructLit(fields) => Expr::StructLit(StructLitExpr {
1628                base: None, // No module prefix for simple `StructName { ... }`
1629                name,
1630                fields,
1631                span: span_from_extra(e),
1632            }),
1633            IdentSuffix::PathCall(function, args) => Expr::AssocFnCall(AssocFnCallExpr {
1634                base: None, // No module prefix for simple `Type::function()`
1635                type_name: name,
1636                type_args: Vec::new(),
1637                function,
1638                args,
1639                span: span_from_extra(e),
1640            }),
1641            IdentSuffix::TypeCallPathCall(type_args, function, args) => {
1642                Expr::AssocFnCall(AssocFnCallExpr {
1643                    base: None,
1644                    type_name: name,
1645                    type_args,
1646                    function,
1647                    args,
1648                    span: span_from_extra(e),
1649                })
1650            }
1651            IdentSuffix::PathStructLit(variant, fields) => Expr::EnumStructLit(EnumStructLitExpr {
1652                base: None,
1653                type_name: name,
1654                variant,
1655                fields,
1656                span: span_from_extra(e),
1657            }),
1658            IdentSuffix::Path(variant) => Expr::Path(PathExpr {
1659                base: None, // No module prefix for simple `Enum::Variant`
1660                type_name: name,
1661                variant,
1662                span: span_from_extra(e),
1663            }),
1664            IdentSuffix::None => Expr::Ident(name),
1665        })
1666        .boxed()
1667}
1668
1669/// Suffix for field access (.field), method call (.method(args)), indexing ([expr]),
1670/// qualified struct literals (.Type { ... }), and qualified paths (.Enum::Variant)
1671#[derive(Clone)]
1672enum Suffix {
1673    /// Simple field access: .field
1674    Field(Ident),
1675    /// Tuple field access: .0, .1, ... (ADR-0048).
1676    /// The u32 is the index; the Span is the position of the integer literal.
1677    TupleField(u32, Span),
1678    /// Method call with method name, arguments, and closing paren position
1679    MethodCall(Ident, Vec<CallArg>, u32),
1680    /// Index expression with the inner expression and closing bracket position
1681    Index(Expr, u32),
1682    /// Qualified struct literal: .StructName { fields }
1683    /// NOTE: Not yet wired up due to grammar ambiguity with field access + block
1684    QualifiedStructLit(Ident, Vec<FieldInit>, u32),
1685    /// Qualified path (enum variant): .EnumName::Variant
1686    QualifiedPath(Ident, Ident, u32),
1687    /// Qualified associated function call: .TypeName::function(args)
1688    QualifiedAssocFnCall(Ident, Ident, Vec<CallArg>, u32),
1689    /// Qualified enum struct variant literal: .EnumName::Variant { field: value }
1690    QualifiedEnumStructLit(Ident, Ident, Vec<FieldInit>, u32),
1691}
1692
1693/// Wraps a primary expression parser with field access, method call, and indexing suffixes
1694fn with_suffix_parser<'src, I>(
1695    primary: impl Parser<'src, I, Expr, ParserExtras<'src>> + Clone + 'src,
1696    expr: GruelParser<'src, I, Expr>,
1697) -> GruelParser<'src, I, Expr>
1698where
1699    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
1700{
1701    // Method call: .ident(args)
1702    let method_call_suffix = just(TokenKind::Dot)
1703        .ignore_then(ident_parser())
1704        .then(
1705            call_args_parser(expr.clone())
1706                .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)),
1707        )
1708        .map_with(|(method, args), e| {
1709            Suffix::MethodCall(method, args, offset_to_u32(e.span().end))
1710        });
1711
1712    // Qualified struct literal: .StructName { field: value, ... }
1713    // We need to distinguish between:
1714    //   - `module.Point { x: 1 }` - qualified struct literal
1715    //   - `obj.field { 1 }` - field access followed by a block expression
1716    //
1717    // The key difference is that struct literals have `{ ident: value, ... }` while
1718    // block expressions have `{ expr }`. We use a lookahead to require that `{` is
1719    // followed by `}` (empty struct) or `ident :` (non-empty struct) to confirm it's
1720    // a struct literal, not field access followed by a block.
1721    //
1722    // Lookahead check: succeeds (without consuming) if `{ }` or `{ ident : `
1723    let struct_lit_lookahead = just(TokenKind::LBrace)
1724        .then(
1725            choice((
1726                // Empty struct: { }
1727                just(TokenKind::RBrace).ignored(),
1728                // Non-empty struct: { ident : ...
1729                select! { TokenKind::Ident(_) => () }
1730                    .then_ignore(just(TokenKind::Colon))
1731                    .ignored(),
1732            ))
1733            // We're just checking the pattern exists, not consuming
1734            .rewind(),
1735        )
1736        .rewind();
1737
1738    // NOTE: Box the lookahead itself to prevent its complex nested type from
1739    // accumulating in qualified_struct_lit_suffix before that parser is boxed.
1740    let struct_lit_lookahead: GruelParser<'src, I, _> = struct_lit_lookahead.boxed();
1741
1742    // NOTE: .boxed() is required here to shorten the monomorphized type name.
1743    // The lookahead creates deeply nested generics that exceed macOS linker limits.
1744    // We split into a boxed head (name + lookahead) and then chain the body.
1745    let struct_lit_name: GruelParser<'src, I, Ident> = just(TokenKind::Dot)
1746        .ignore_then(ident_parser())
1747        .then_ignore(struct_lit_lookahead)
1748        .boxed();
1749    let qualified_struct_lit_suffix = struct_lit_name
1750        .then(
1751            field_inits_parser(expr.clone())
1752                .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)),
1753        )
1754        .map_with(|(name, fields), e| {
1755            Suffix::QualifiedStructLit(name, fields, offset_to_u32(e.span().end))
1756        })
1757        .boxed();
1758
1759    // NOTE: .boxed() on the shared head prevents type accumulation in both
1760    // qualified_assoc_fn_suffix and qualified_path_suffix.
1761    let type_and_member: GruelParser<'src, I, (Ident, Ident)> = just(TokenKind::Dot)
1762        .ignore_then(ident_parser())
1763        .then_ignore(just(TokenKind::ColonColon))
1764        .then(ident_parser())
1765        .boxed();
1766
1767    // Qualified associated function call: .TypeName::function(args)
1768    let qualified_assoc_fn_suffix = type_and_member
1769        .clone()
1770        .then(
1771            call_args_parser(expr.clone())
1772                .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)),
1773        )
1774        .map_with(|((type_name, function), args), e| {
1775            Suffix::QualifiedAssocFnCall(type_name, function, args, offset_to_u32(e.span().end))
1776        });
1777
1778    // Qualified enum struct variant literal: .EnumName::Variant { field: value, ... }
1779    let qualified_enum_struct_lit_suffix = type_and_member
1780        .clone()
1781        .then(
1782            field_inits_parser(expr.clone())
1783                .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)),
1784        )
1785        .map_with(|((type_name, variant), fields), e| {
1786            Suffix::QualifiedEnumStructLit(type_name, variant, fields, offset_to_u32(e.span().end))
1787        });
1788
1789    // Qualified path (enum variant): .EnumName::Variant
1790    // We capture the end position from the variant ident before the negative lookahead
1791    let qualified_path_suffix = type_and_member
1792        .map(|(type_name, variant): (Ident, Ident)| {
1793            let end = variant.span.end;
1794            (type_name, variant, end)
1795        })
1796        .then_ignore(none_of([TokenKind::LParen, TokenKind::LBrace]).rewind())
1797        .map(|(type_name, variant, end)| Suffix::QualifiedPath(type_name, variant, end));
1798
1799    // Field access: .ident (but NOT followed by '(', '::', or struct literal pattern)
1800    // The qualified_struct_lit_suffix is tried first and uses lookahead, so field_suffix
1801    // only matches when we're certain it's field access, not a qualified struct literal.
1802    let field_suffix = just(TokenKind::Dot)
1803        .ignore_then(ident_parser())
1804        .then_ignore(none_of([TokenKind::LParen, TokenKind::ColonColon]).rewind())
1805        .map(Suffix::Field);
1806
1807    // Tuple field access: .N where N is a non-negative integer literal (ADR-0048).
1808    //
1809    // Note: the lexer tokenises `0.1` / `1e10` as a single `Float` token, so
1810    // `t.0.1` and `t.1e10` fail to parse as nested tuple access. Users must
1811    // write `(t.0).1` for nested access. Float re-splitting in field position
1812    // is deferred to a future ADR.
1813    //
1814    // Indices larger than u32::MAX are clamped to u32::MAX; tuples cannot have
1815    // more than u32::MAX elements, so sema will report this as out-of-bounds.
1816    let tuple_field_suffix = just(TokenKind::Dot)
1817        .ignore_then(select! {
1818            TokenKind::Int(n) = e => (n, span_from_extra(e)),
1819        })
1820        .map(|(n, span)| {
1821            let idx = if n > u32::MAX as u64 {
1822                u32::MAX
1823            } else {
1824                n as u32
1825            };
1826            Suffix::TupleField(idx, span)
1827        });
1828
1829    // ADR-0064: range subscripts. Inside `[ … ]`, accept either a regular
1830    // expression (existing array indexing) or a range form: `..`, `..hi`,
1831    // `lo..`, `lo..hi`. The parser produces `Expr::Range(_)` for the range
1832    // forms; sema enforces that ranges only appear under `&` / `&mut`.
1833    //
1834    // The lexer has no `DotDot` token — `..` is two adjacent `.` tokens
1835    // (same convention as the rest-pattern parser).
1836    let dot_dot = just(TokenKind::Dot).then(just(TokenKind::Dot));
1837
1838    let range_or_expr = choice((
1839        // `..` or `..hi` — leading `..` makes this unambiguous.
1840        dot_dot
1841            .ignore_then(expr.clone().or_not())
1842            .map_with(|hi, e| {
1843                Expr::Range(RangeExpr {
1844                    lo: None,
1845                    hi: hi.map(Box::new),
1846                    span: span_from_extra(e),
1847                })
1848            }),
1849        // `expr` optionally followed by `..` and optionally another expr.
1850        // No `..` => plain index expression.
1851        expr.clone()
1852            .then(dot_dot.ignore_then(expr.clone().or_not()).or_not())
1853            .map_with(|(lhs, suffix), e| match suffix {
1854                Some(hi) => Expr::Range(RangeExpr {
1855                    lo: Some(Box::new(lhs)),
1856                    hi: hi.map(Box::new),
1857                    span: span_from_extra(e),
1858                }),
1859                None => lhs,
1860            }),
1861    ));
1862
1863    let index_suffix = range_or_expr
1864        .delimited_by(just(TokenKind::LBracket), just(TokenKind::RBracket))
1865        .map_with(|index, e| Suffix::Index(index, offset_to_u32(e.span().end)));
1866
1867    // Order matters: more specific patterns must come before less specific ones
1868    // - qualified_assoc_fn_suffix before qualified_path_suffix (both have ::)
1869    // - method_call_suffix before field_suffix (both start with .ident)
1870    // - qualified_struct_lit_suffix before field_suffix (uses lookahead for { ident: } pattern)
1871    // - qualified_path_suffix before field_suffix (both start with .ident)
1872    //
1873    // NOTE: .boxed() is required here to shorten the monomorphized type name.
1874    // Without it, macOS's ld64 linker fails with "symbol name too long" because
1875    // the 6-way choice creates extremely long generic type names.
1876    primary
1877        .foldl(
1878            choice((
1879                method_call_suffix.boxed(),
1880                qualified_assoc_fn_suffix.boxed(),
1881                qualified_enum_struct_lit_suffix.boxed(),
1882                qualified_struct_lit_suffix,
1883                qualified_path_suffix.boxed(),
1884                field_suffix.boxed(),
1885                tuple_field_suffix.boxed(),
1886                index_suffix.boxed(),
1887            ))
1888            .boxed()
1889            .repeated(),
1890            |base, suffix| match suffix {
1891                Suffix::Field(field) => {
1892                    // Extend the base span to include the field, preserving file_id
1893                    let span = base.span().extend_to(field.span.end);
1894                    Expr::Field(FieldExpr {
1895                        base: Box::new(base),
1896                        field,
1897                        span,
1898                    })
1899                }
1900                Suffix::TupleField(index, index_span) => {
1901                    let span = base.span().extend_to(index_span.end);
1902                    Expr::TupleIndex(TupleIndexExpr {
1903                        base: Box::new(base),
1904                        index,
1905                        span,
1906                        index_span,
1907                    })
1908                }
1909                Suffix::MethodCall(method, args, end) => {
1910                    // Extend the base span to the end of the call, preserving file_id
1911                    let span = base.span().extend_to(end);
1912                    Expr::MethodCall(MethodCallExpr {
1913                        receiver: Box::new(base),
1914                        method,
1915                        args,
1916                        span,
1917                    })
1918                }
1919                Suffix::Index(index, end) => {
1920                    // Extend the base span to the end of the index, preserving file_id
1921                    let span = base.span().extend_to(end);
1922                    Expr::Index(IndexExpr {
1923                        base: Box::new(base),
1924                        index: Box::new(index),
1925                        span,
1926                    })
1927                }
1928                Suffix::QualifiedStructLit(name, fields, end) => {
1929                    // module.StructName { ... } → StructLitExpr with base
1930                    let span = base.span().extend_to(end);
1931                    Expr::StructLit(StructLitExpr {
1932                        base: Some(Box::new(base)),
1933                        name,
1934                        fields,
1935                        span,
1936                    })
1937                }
1938                Suffix::QualifiedPath(type_name, variant, end) => {
1939                    // module.EnumName::Variant → PathExpr with base
1940                    let span = base.span().extend_to(end);
1941                    Expr::Path(PathExpr {
1942                        base: Some(Box::new(base)),
1943                        type_name,
1944                        variant,
1945                        span,
1946                    })
1947                }
1948                Suffix::QualifiedEnumStructLit(type_name, variant, fields, end) => {
1949                    // module.EnumName::Variant { ... } → EnumStructLitExpr with base
1950                    let span = base.span().extend_to(end);
1951                    Expr::EnumStructLit(EnumStructLitExpr {
1952                        base: Some(Box::new(base)),
1953                        type_name,
1954                        variant,
1955                        fields,
1956                        span,
1957                    })
1958                }
1959                Suffix::QualifiedAssocFnCall(type_name, function, args, end) => {
1960                    // module.TypeName::function(args) → AssocFnCallExpr with base
1961                    let span = base.span().extend_to(end);
1962                    Expr::AssocFnCall(AssocFnCallExpr {
1963                        base: Some(Box::new(base)),
1964                        type_name,
1965                        type_args: Vec::new(),
1966                        function,
1967                        args,
1968                        span,
1969                    })
1970                }
1971            },
1972        )
1973        .boxed()
1974}
1975
1976/// Atom parser - primary expressions (literals, identifiers, parens, blocks, control flow)
1977fn atom_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, Expr>
1978where
1979    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
1980{
1981    // Self expression (in method bodies)
1982    let self_expr = select! {
1983        TokenKind::SelfValue = e => Expr::SelfExpr(SelfExpr { span: span_from_extra(e) }),
1984    };
1985
1986    // Parenthesized expression or tuple literal.
1987    //
1988    // `(e)`         -> ParenExpr
1989    // `(e,)`        -> TupleExpr with one element
1990    // `(e, f, ...)` -> TupleExpr with 2+ elements
1991    // `()` is handled separately by `unit_lit` (see literal_parser).
1992    //
1993    // Implementation: parse `LParen expr`, then optionally `Comma expr_list`,
1994    // then `RParen`. If the comma is absent we have a paren; if it's present
1995    // we have a tuple (possibly 1-arity if no more elements follow).
1996    let paren_or_tuple = just(TokenKind::LParen)
1997        .ignore_then(expr.clone())
1998        .then(
1999            just(TokenKind::Comma)
2000                .ignore_then(
2001                    expr.clone()
2002                        .separated_by(just(TokenKind::Comma))
2003                        .allow_trailing()
2004                        .collect::<Vec<_>>(),
2005                )
2006                .or_not(),
2007        )
2008        .then_ignore(just(TokenKind::RParen))
2009        .map_with(|(first, rest), e| match rest {
2010            None => Expr::Paren(ParenExpr {
2011                inner: Box::new(first),
2012                span: span_from_extra(e),
2013            }),
2014            Some(rest) => {
2015                let mut elems = Vec::with_capacity(1 + rest.len());
2016                elems.push(first);
2017                elems.extend(rest);
2018                Expr::Tuple(TupleExpr {
2019                    elems,
2020                    span: span_from_extra(e),
2021                })
2022            }
2023        });
2024    let paren_expr = paren_or_tuple;
2025
2026    // Block expression
2027    let block_expr = block_parser(expr.clone());
2028
2029    // Comptime block expression: comptime { expr }
2030    let comptime_expr = just(TokenKind::Comptime)
2031        .ignore_then(block_parser(expr.clone()))
2032        .map_with(|inner_expr, e| {
2033            Expr::Comptime(ComptimeBlockExpr {
2034                expr: Box::new(inner_expr),
2035                span: span_from_extra(e),
2036            })
2037        });
2038
2039    // Checked block expression: checked { expr }
2040    // Unchecked operations are only allowed inside checked blocks
2041    let checked_expr = just(TokenKind::Checked)
2042        .ignore_then(block_parser(expr.clone()))
2043        .map_with(|inner_expr, e| {
2044            Expr::Checked(CheckedBlockExpr {
2045                expr: Box::new(inner_expr),
2046                span: span_from_extra(e),
2047            })
2048        });
2049
2050    // Intrinsic argument: can be either a type or an expression
2051    // We parse as type only for unambiguous type syntax (primitives, (), !, [T;N])
2052    // Bare identifiers are parsed as expressions since they could be variables
2053    let unambiguous_type = {
2054        // Unit type: ()
2055        let unit_type = just(TokenKind::LParen)
2056            .then(just(TokenKind::RParen))
2057            .map_with(|_, e| IntrinsicArg::Type(TypeExpr::Unit(span_from_extra(e))));
2058
2059        // Never type: !
2060        let never_type = just(TokenKind::Bang)
2061            .map_with(|_, e| IntrinsicArg::Type(TypeExpr::Never(span_from_extra(e))));
2062
2063        // Array type: [T; N]
2064        let array_type = just(TokenKind::LBracket)
2065            .ignore_then(type_parser())
2066            .then_ignore(just(TokenKind::Semi))
2067            .then(select! {
2068                TokenKind::Int(n) => n,
2069            })
2070            .then_ignore(just(TokenKind::RBracket))
2071            .map_with(|(element, length), e| {
2072                IntrinsicArg::Type(TypeExpr::Array {
2073                    element: Box::new(element),
2074                    length,
2075                    span: span_from_extra(e),
2076                })
2077            });
2078
2079        // Primitive type keywords (these can't be variable names)
2080        let primitive_type = primitive_type_parser().map(IntrinsicArg::Type);
2081
2082        // ADR-0079: `Self` as a type argument (e.g.
2083        // `@type_info(Self)` inside a `derive` body). The bare
2084        // keyword can't appear as a value, so it's unambiguous in
2085        // this slot.
2086        let self_type_arg = just(TokenKind::SelfType).map_with(|_, e| {
2087            let span = span_from_extra(e);
2088            IntrinsicArg::Type(TypeExpr::Named(Ident {
2089                name: e.state().syms.self_type,
2090                span,
2091            }))
2092        });
2093
2094        choice((
2095            unit_type.boxed(),
2096            never_type.boxed(),
2097            array_type.boxed(),
2098            primitive_type.boxed(),
2099            self_type_arg.boxed(),
2100        ))
2101        .boxed()
2102    };
2103
2104    // Try unambiguous type syntax first, then fall back to expression.
2105    // Boxed so the SeparatedBy type is short when used in intrinsic_call args.
2106    let intrinsic_arg: GruelParser<I, IntrinsicArg> = choice((
2107        unambiguous_type,
2108        expr.clone().map(IntrinsicArg::Expr).boxed(),
2109    ))
2110    .boxed();
2111
2112    // Shared intrinsic args parser (boxed to keep downstream types short).
2113    let intrinsic_args: GruelParser<I, Vec<IntrinsicArg>> = intrinsic_arg
2114        .separated_by(just(TokenKind::Comma))
2115        .allow_trailing()
2116        .collect::<Vec<_>>()
2117        .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen))
2118        .boxed();
2119
2120    // Intrinsic call: @name(args)
2121    let intrinsic_call: GruelParser<I, Expr> = just(TokenKind::At)
2122        .ignore_then(ident_parser())
2123        .then(intrinsic_args.clone())
2124        .map_with(|(name, args), e| {
2125            Expr::IntrinsicCall(IntrinsicCallExpr {
2126                name,
2127                args,
2128                span: span_from_extra(e),
2129            })
2130        })
2131        .boxed();
2132
2133    // @import(args) - lexer tokenizes @import as a single token with interned "import" Spur
2134    let import_call: GruelParser<I, Expr> = select! {
2135        TokenKind::AtImport(import_spur) = e => (import_spur, span_from_extra(e)),
2136    }
2137    .then(intrinsic_args)
2138    .map_with(|((import_spur, import_span), args), e| {
2139        Expr::IntrinsicCall(IntrinsicCallExpr {
2140            name: Ident {
2141                name: import_spur,
2142                span: import_span,
2143            },
2144            args,
2145            span: span_from_extra(e),
2146        })
2147    })
2148    .boxed();
2149
2150    // Combined intrinsic parser: try @import first, then general @name pattern
2151    let any_intrinsic_call: GruelParser<I, Expr> = choice((import_call, intrinsic_call)).boxed();
2152
2153    // Array literal: [expr, expr, ...]
2154    let array_lit = args_parser(expr.clone())
2155        .delimited_by(just(TokenKind::LBracket), just(TokenKind::RBracket))
2156        .map_with(|elements, e| {
2157            Expr::ArrayLit(ArrayLitExpr {
2158                elements,
2159                span: span_from_extra(e),
2160            })
2161        });
2162
2163    // ADR-0071: associated-function call on the `char` primitive:
2164    //   `char::name(args)` — produces an `AssocFnCall` with type_name = "char".
2165    // This must be tried before `type_lit_expr` because `type_lit_expr`
2166    // consumes a bare `char` token and the `::name(...)` tail wouldn't fit.
2167    let char_assoc_fn_call = just(TokenKind::Char)
2168        .ignore_then(just(TokenKind::ColonColon))
2169        .ignore_then(ident_parser())
2170        .then(
2171            call_args_parser(expr.clone())
2172                .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)),
2173        )
2174        .map_with(
2175            |(function, args), e: &mut MapExtra<'src, '_, I, ParserExtras<'src>>| {
2176                let syms = e.state().0.syms;
2177                let span = span_from_extra(e);
2178                Expr::AssocFnCall(AssocFnCallExpr {
2179                    base: None,
2180                    type_name: Ident {
2181                        name: syms.char,
2182                        span,
2183                    },
2184                    type_args: Vec::new(),
2185                    function,
2186                    args,
2187                    span,
2188                })
2189            },
2190        );
2191
2192    // Type literal expression: i32, bool, etc. used as values
2193    // This enables generic function calls like identity(i32, 42)
2194    let type_lit_expr = primitive_type_parser().map_with(|type_expr, e| {
2195        Expr::TypeLit(TypeLitExpr {
2196            type_expr,
2197            span: span_from_extra(e),
2198        })
2199    });
2200
2201    // `Self` used as a value — e.g. `helper(Self, T)` passing the
2202    // current struct's type as a comptime argument. The more specific
2203    // `Self { ... }` (struct literal), `Self::Variant(...)`
2204    // (associated function / enum variant), and `Self { ... }` enum
2205    // struct literal forms are parsed earlier in the choice and bind
2206    // first; bare `Self` here falls through to a plain type-literal
2207    // expression.
2208    let self_as_value_expr = just(TokenKind::SelfType).map_with(|_, e| {
2209        let span = span_from_extra(e);
2210        Expr::TypeLit(TypeLitExpr {
2211            type_expr: TypeExpr::Named(Ident {
2212                name: e.state().syms.self_type,
2213                span,
2214            }),
2215            span,
2216        })
2217    });
2218
2219    // Anonymous struct type as expression: struct { field: Type, ... fn method(...) { ... } ... }
2220    // This enables comptime type construction like:
2221    //   fn Pair(comptime T: type) -> type { struct { first: T, second: T } }
2222    // With methods (Zig-style):
2223    //   fn Vec(comptime T: type) -> type { struct { ptr: u64, fn push(self, item: T) { ... } } }
2224    let anon_struct_field: GruelParser<'src, I, AnonStructField> = ident_parser()
2225        .then_ignore(just(TokenKind::Colon))
2226        .then(type_parser())
2227        .map_with(|(name, field_ty), e| AnonStructField {
2228            doc: None,
2229            name,
2230            ty: field_ty,
2231            span: span_from_extra(e),
2232        })
2233        .boxed();
2234
2235    let anon_struct_fields: GruelParser<'src, I, Vec<AnonStructField>> = anon_struct_field
2236        .separated_by(just(TokenKind::Comma))
2237        .allow_trailing()
2238        .collect::<Vec<_>>()
2239        .boxed();
2240
2241    // Parse method for anonymous struct using inline method parsing
2242    // Methods inside anonymous structs follow the same syntax as impl block methods
2243    let anon_struct_method = anon_struct_method_parser(expr.clone());
2244
2245    // ADR-0083 Phase 4: posture is now declared exclusively via the
2246    // `@mark(...)` directive. The `posture` field on the AST nodes survives
2247    // only as the directive-derived storage; sema populates it from the
2248    // directive list at registration time. No posture keyword is parsed
2249    // here.
2250    let anon_struct_header: GruelParser<'src, I, (Directives, Vec<AnonStructField>, Vec<Method>)> =
2251        directives_parser()
2252            .then_ignore(just(TokenKind::Struct))
2253            .then_ignore(just(TokenKind::LBrace))
2254            .then(anon_struct_fields)
2255            .then(
2256                // Then parse methods (not comma-separated, each ends with })
2257                anon_struct_method.repeated().collect::<Vec<_>>(),
2258            )
2259            .map(|((directives, fields), methods)| (directives, fields, methods))
2260            .boxed();
2261
2262    let anon_struct_type_expr = anon_struct_header
2263        .then_ignore(just(TokenKind::RBrace))
2264        .map_with(|(directives, fields, methods), e| {
2265            let span = span_from_extra(e);
2266            Expr::TypeLit(TypeLitExpr {
2267                type_expr: TypeExpr::AnonymousStruct {
2268                    directives,
2269                    posture: Posture::Affine,
2270                    fields,
2271                    methods,
2272                    span,
2273                },
2274                span,
2275            })
2276        });
2277
2278    // Anonymous function expression (ADR-0055): fn(params) -> ret { body }
2279    //
2280    // Reuses the named-function production verbatim, minus the identifier. At
2281    // expression position `fn` is unambiguous — it is never otherwise an
2282    // expression starter — so this does not conflict with anything.
2283    let anon_fn_expr: GruelParser<'src, I, Expr> = just(TokenKind::Fn)
2284        .ignore_then(params_parser().delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)))
2285        .then(just(TokenKind::Arrow).ignore_then(type_parser()).or_not())
2286        .then(block_parser(expr.clone()))
2287        .map_with(|((params, return_type), body), e| {
2288            let body_block = match body {
2289                Expr::Block(b) => b,
2290                _ => unreachable!("block_parser always returns Expr::Block"),
2291            };
2292            Expr::AnonFn(AnonFnExpr {
2293                params,
2294                return_type,
2295                body: body_block,
2296                span: span_from_extra(e),
2297            })
2298        })
2299        .boxed();
2300
2301    // Anonymous enum type as expression: enum { Variant, Variant(T), ... fn method(...) { ... } ... }
2302    // This enables comptime type construction like:
2303    //   fn Option(comptime T: type) -> type { enum { Some(T), None } }
2304    let anon_enum_method = anon_struct_method_parser(expr.clone());
2305
2306    // ADR-0083 Phase 4: same posture-keyword retirement for anonymous
2307    // enum literals — directive list only.
2308    let anon_enum_type_expr = directives_parser()
2309        .then_ignore(just(TokenKind::Enum))
2310        .then_ignore(just(TokenKind::LBrace))
2311        .then(enum_variants_parser())
2312        .then(
2313            // Then parse methods (not comma-separated, each ends with })
2314            anon_enum_method.repeated().collect::<Vec<_>>(),
2315        )
2316        .then_ignore(just(TokenKind::RBrace))
2317        .map_with(|((directives, variants), methods), e| {
2318            let span = span_from_extra(e);
2319            Expr::TypeLit(TypeLitExpr {
2320                type_expr: TypeExpr::AnonymousEnum {
2321                    directives,
2322                    posture: Posture::Affine,
2323                    variants,
2324                    methods,
2325                    span,
2326                },
2327                span,
2328            })
2329        });
2330
2331    // Anonymous interface type as expression (ADR-0057):
2332    //   interface { fn name(self [, params]) [-> RetType]; ... }
2333    // Used inside `fn ... -> type` bodies to build parameterized
2334    // interfaces.
2335    let anon_interface_type_expr = just(TokenKind::Interface)
2336        .ignore_then(just(TokenKind::LBrace))
2337        .ignore_then(interface_method_sig_parser().repeated().collect::<Vec<_>>())
2338        .then_ignore(just(TokenKind::RBrace))
2339        .map_with(|methods, e| {
2340            let span = span_from_extra(e);
2341            Expr::TypeLit(TypeLitExpr {
2342                type_expr: TypeExpr::AnonymousInterface { methods, span },
2343                span,
2344            })
2345        });
2346
2347    // Self type expression: Self { field: value } (struct literal with Self as type)
2348    // This enables constructing instances of anonymous struct types from methods
2349    let self_type_expr = just(TokenKind::SelfType)
2350        .ignore_then(
2351            field_inits_parser(expr.clone())
2352                .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)),
2353        )
2354        .map_with(|fields, e| {
2355            let span = span_from_extra(e);
2356            Expr::StructLit(StructLitExpr {
2357                base: None,
2358                name: Ident {
2359                    name: e.state().syms.self_type,
2360                    span,
2361                },
2362                fields,
2363                span,
2364            })
2365        });
2366
2367    // Self::Variant(args) — associated function call on Self (for anonymous enum variant construction)
2368    let self_assoc_fn_call = just(TokenKind::SelfType)
2369        .ignore_then(just(TokenKind::ColonColon))
2370        .ignore_then(ident_parser())
2371        .then(
2372            call_args_parser(expr.clone())
2373                .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)),
2374        )
2375        .map_with(|(function, args), e| {
2376            let span = span_from_extra(e);
2377            Expr::AssocFnCall(AssocFnCallExpr {
2378                base: None,
2379                type_name: Ident {
2380                    name: e.state().syms.self_type,
2381                    span,
2382                },
2383                type_args: Vec::new(),
2384                function,
2385                args,
2386                span,
2387            })
2388        });
2389
2390    // Self::Variant { field: value, ... } — struct variant construction on Self
2391    let self_enum_struct_lit = just(TokenKind::SelfType)
2392        .ignore_then(just(TokenKind::ColonColon))
2393        .ignore_then(ident_parser())
2394        .then(
2395            field_inits_parser(expr.clone())
2396                .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)),
2397        )
2398        .map_with(|(variant, fields), e| {
2399            let span = span_from_extra(e);
2400            Expr::EnumStructLit(EnumStructLitExpr {
2401                base: None,
2402                type_name: Ident {
2403                    name: e.state().syms.self_type,
2404                    span,
2405                },
2406                variant,
2407                fields,
2408                span,
2409            })
2410        });
2411
2412    // Self::Variant — unit variant on Self (for anonymous enum variant construction)
2413    let self_enum_variant = just(TokenKind::SelfType)
2414        .ignore_then(just(TokenKind::ColonColon))
2415        .ignore_then(ident_parser())
2416        .then_ignore(none_of([TokenKind::LParen, TokenKind::LBrace]).rewind())
2417        .map_with(|variant, e| {
2418            let span = span_from_extra(e);
2419            Expr::Path(PathExpr {
2420                base: None,
2421                type_name: Ident {
2422                    name: e.state().syms.self_type,
2423                    span,
2424                },
2425                variant,
2426                span,
2427            })
2428        });
2429
2430    // Primary expression (before field access and indexing)
2431    // Note: literal_parser() includes unit_lit which must come before paren_expr
2432    // so () is parsed as unit, not empty parens
2433    // Note: self_expr must come before call_and_access_parser since self is a keyword
2434    // Note: self_type_expr must come before call_and_access_parser since Self is a keyword
2435    // Note: comptime_expr and checked_expr must come before block_expr since they start with keywords
2436    // Note: type_lit_expr must come before call_and_access_parser since type names are keywords
2437    // Note: anon_struct_type_expr / anon_enum_type_expr must come before
2438    // any_intrinsic_call: ADR-0083's `@mark(...)` is a directive that
2439    // appears in the directive list of an anonymous type literal, but the
2440    // parser otherwise parses `@name(args)` as an intrinsic call. Trying the
2441    // anon-type form first lets `directives_parser()` consume the leading
2442    // `@mark(copy)`, then the trailing `struct` / `enum` keyword
2443    // disambiguates from a plain intrinsic call.
2444    //
2445    // NOTE: Split into sub-groups to keep Choice<tuple> symbol length < 4K.
2446    // 13 elements of Boxed<I,Expr,E> in one tuple would produce ~7K symbols on macOS.
2447    let primary_a: GruelParser<'src, I, Expr> = choice((
2448        literal_parser(),
2449        control_flow_parser(expr.clone()),
2450        self_expr.boxed(),
2451        self_assoc_fn_call.boxed(),
2452        self_enum_struct_lit.boxed(),
2453        self_enum_variant.boxed(),
2454        self_type_expr.boxed(),
2455        anon_struct_type_expr.boxed(),
2456        anon_enum_type_expr.boxed(),
2457        any_intrinsic_call.boxed(),
2458    ))
2459    .boxed();
2460    let primary_b: GruelParser<'src, I, Expr> = choice((
2461        array_lit.boxed(),
2462        anon_interface_type_expr.boxed(),
2463        anon_fn_expr,
2464        // ADR-0071: must come before type_lit_expr (which consumes the `char` token alone).
2465        char_assoc_fn_call.boxed(),
2466        type_lit_expr.boxed(),
2467        // Bare `Self` as a value — the more-specific Self forms above
2468        // (`self_type_expr` for `Self { ... }`, `self_assoc_fn_call`
2469        // for `Self::method(...)`, etc.) are tried first; this falls
2470        // through to it for `Self` followed by `,`, `)`, `;` etc.
2471        self_as_value_expr.boxed(),
2472        call_and_access_parser(expr.clone()),
2473        paren_expr.boxed(),
2474    ))
2475    .boxed();
2476    let primary_c: GruelParser<'src, I, Expr> = choice((
2477        comptime_expr.boxed(),
2478        checked_expr.boxed(),
2479        block_expr.boxed(),
2480    ))
2481    .boxed();
2482    let primary: GruelParser<'src, I, Expr> = choice((primary_a, primary_b, primary_c)).boxed();
2483
2484    // Wrap primary expressions with field access, method call, and indexing suffixes
2485    with_suffix_parser(primary, expr)
2486}
2487
2488/// A block item is either a statement or an expression (potentially the final one)
2489#[derive(Debug, Clone)]
2490enum BlockItem {
2491    Statement(Statement),
2492    Expr(Expr),
2493}
2494
2495/// What token follows an expression in a block (used to determine its role)
2496#[derive(Debug, Clone, Copy)]
2497enum ExprFollower {
2498    /// Semicolon follows - expression is a statement
2499    Semi,
2500    /// Right brace follows (not consumed) - expression is the final/return value
2501    RBrace,
2502    /// Some other token follows - only valid for control flow expressions
2503    Other,
2504    /// End of input - only valid for control flow expressions
2505    End,
2506}
2507
2508/// Parser for a let binding pattern (ADR-0049): delegates to the generic
2509/// `pattern_parser()`. Refutability is enforced by sema (Phase 3).
2510/// Nested pattern shapes are accepted unconditionally after Phase 8
2511/// stabilization.
2512fn let_pattern_parser<'src, I>() -> GruelParser<'src, I, Pattern>
2513where
2514    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2515{
2516    pattern_parser()
2517}
2518
2519/// Parser for let statements: [@directive]* let [mut] pattern [: type] = expr;
2520fn let_statement_parser<'src, I>(
2521    expr: GruelParser<'src, I, Expr>,
2522) -> GruelParser<'src, I, Statement>
2523where
2524    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2525{
2526    // Box after 2 thens to keep accumulated type short.
2527    let let_head: GruelParser<I, (Directives, bool, Pattern)> = directives_parser()
2528        .then(just(TokenKind::Let).ignore_then(just(TokenKind::Mut).or_not().map(|m| m.is_some())))
2529        .then(let_pattern_parser())
2530        .map(|((d, m), p)| (d, m, p))
2531        .boxed();
2532
2533    let let_tail: GruelParser<I, (Option<TypeExpr>, Expr)> = just(TokenKind::Colon)
2534        .ignore_then(type_parser())
2535        .or_not()
2536        .then(just(TokenKind::Eq).ignore_then(expr))
2537        .then_ignore(just(TokenKind::Semi))
2538        .boxed();
2539
2540    let_head
2541        .then(let_tail)
2542        .map_with(|((directives, is_mut, pattern), (ty, init)), e| {
2543            Statement::Let(LetStatement {
2544                directives,
2545                is_mut,
2546                pattern,
2547                ty,
2548                init: Box::new(init),
2549                span: span_from_extra(e),
2550            })
2551        })
2552        .boxed()
2553}
2554
2555/// Suffix for assignment targets: either .field or [index]
2556#[derive(Clone)]
2557enum AssignSuffix {
2558    Field(Ident),
2559    Index(Expr),
2560}
2561
2562/// Parser for assignment target: variable, field access, or index access
2563/// Parses: name or name.field or name[idx] or name.field[idx].field...
2564fn assign_target_parser<'src, I>(
2565    expr: GruelParser<'src, I, Expr>,
2566) -> GruelParser<'src, I, AssignTarget>
2567where
2568    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2569{
2570    let field_suffix = just(TokenKind::Dot)
2571        .ignore_then(ident_parser())
2572        .map(AssignSuffix::Field);
2573
2574    let index_suffix = expr
2575        .delimited_by(just(TokenKind::LBracket), just(TokenKind::RBracket))
2576        .map(AssignSuffix::Index);
2577
2578    // Assignment base: a regular identifier OR `self` (so a method body can
2579    // write through `&mut self`, e.g. `self.value = ...`). Plain `self =
2580    // expr;` is rejected later in sema since `self` is not a place that can
2581    // be rebound.
2582    let self_base = just(TokenKind::SelfValue)
2583        .map_with(|_, e| {
2584            Expr::SelfExpr(SelfExpr {
2585                span: span_from_extra(e),
2586            })
2587        })
2588        .boxed();
2589    let ident_base = ident_parser().map(Expr::Ident).boxed();
2590    let assign_base = choice((self_base, ident_base)).boxed();
2591
2592    assign_base
2593        .then(
2594            choice((field_suffix.boxed(), index_suffix.boxed()))
2595                .repeated()
2596                .collect::<Vec<_>>(),
2597        )
2598        .try_map(|(base_expr_init, suffixes), span| {
2599            if suffixes.is_empty() {
2600                // Simple variable target: only `name = ...` is legal.
2601                // `self = ...` is not — `self` is a parameter binding that
2602                // can't be rebound. Reject at parse time.
2603                return match base_expr_init {
2604                    Expr::Ident(ident) => Ok(AssignTarget::Var(ident)),
2605                    _ => Err(chumsky::error::Rich::custom(
2606                        span,
2607                        "cannot assign to `self`",
2608                    )),
2609                };
2610            }
2611            // Chain of field/index accesses: x.a[0].b...
2612            // Build up the expression from left to right, consuming suffixes by value.
2613            let mut base_expr = base_expr_init;
2614            let mut suffixes_iter = suffixes.into_iter().peekable();
2615            while let Some(suffix) = suffixes_iter.next() {
2616                let is_last = suffixes_iter.peek().is_none();
2617                if is_last {
2618                    return Ok(match suffix {
2619                        AssignSuffix::Field(field) => {
2620                            let span = Span::new(base_expr.span().start, field.span.end);
2621                            AssignTarget::Field(FieldExpr {
2622                                base: Box::new(base_expr),
2623                                field,
2624                                span,
2625                            })
2626                        }
2627                        AssignSuffix::Index(index) => {
2628                            let span = Span::new(base_expr.span().start, index.span().end);
2629                            AssignTarget::Index(IndexExpr {
2630                                base: Box::new(base_expr),
2631                                index: Box::new(index),
2632                                span,
2633                            })
2634                        }
2635                    });
2636                }
2637                // Build intermediate expressions
2638                match suffix {
2639                    AssignSuffix::Field(field) => {
2640                        let span = Span::new(base_expr.span().start, field.span.end);
2641                        base_expr = Expr::Field(FieldExpr {
2642                            base: Box::new(base_expr),
2643                            field,
2644                            span,
2645                        });
2646                    }
2647                    AssignSuffix::Index(index) => {
2648                        let span = Span::new(base_expr.span().start, index.span().end);
2649                        base_expr = Expr::Index(IndexExpr {
2650                            base: Box::new(base_expr),
2651                            index: Box::new(index),
2652                            span,
2653                        });
2654                    }
2655                }
2656            }
2657            unreachable!("suffixes was non-empty so the loop must have returned")
2658        })
2659        .boxed()
2660}
2661
2662/// Parser for assignment statements: target = expr;
2663/// Supports variable (x = 5), field (point.x = 5), and index (arr[0] = 5) assignment
2664fn assign_statement_parser<'src, I>(
2665    expr: GruelParser<'src, I, Expr>,
2666) -> GruelParser<'src, I, Statement>
2667where
2668    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2669{
2670    assign_target_parser(expr.clone())
2671        .then_ignore(just(TokenKind::Eq))
2672        .then(expr)
2673        .then_ignore(just(TokenKind::Semi))
2674        .map_with(|(target, value), e| {
2675            Statement::Assign(AssignStatement {
2676                target,
2677                value: Box::new(value),
2678                span: span_from_extra(e),
2679            })
2680        })
2681        .boxed()
2682}
2683
2684/// Returns true if the expression is a control flow construct or block that can appear
2685/// as a statement without a trailing semicolon.
2686///
2687/// This includes:
2688/// - Control flow: if, while, match, loop, break, continue, return
2689/// - Block expressions: { ... }
2690///
2691/// Block expressions are included because they are syntactically similar to control flow:
2692/// they are compound statements that naturally terminate without a semicolon.
2693fn is_control_flow_expr(e: &Expr) -> bool {
2694    matches!(
2695        e,
2696        Expr::If(_)
2697            | Expr::Match(_)
2698            | Expr::While(_)
2699            | Expr::For(_)
2700            | Expr::ComptimeUnrollFor(_)
2701            | Expr::Loop(_)
2702            | Expr::Break(_)
2703            | Expr::Continue(_)
2704            | Expr::Return(_)
2705            | Expr::Block(_)
2706    )
2707}
2708
2709/// Returns true if the expression diverges (has the Never type).
2710/// These expressions can be promoted to the final expression of a block
2711/// since Never coerces to any type.
2712fn is_diverging_expr(e: &Expr) -> bool {
2713    matches!(
2714        e,
2715        Expr::Break(_) | Expr::Continue(_) | Expr::Return(_) | Expr::Loop(_)
2716    )
2717}
2718
2719/// Parses a single item within a block.
2720///
2721/// # Block Item Grammar
2722///
2723/// A block contains zero or more items. Each item is one of:
2724/// - **Let statement**: `let x = expr;` (always requires semicolon)
2725/// - **Assignment statement**: `target = expr;` (always requires semicolon)
2726/// - **Expression statement**: `expr;` (requires semicolon for most expressions)
2727/// - **Control flow statement**: `if/while/match/loop/break/continue/return ...`
2728///   (no semicolon needed when mid-block)
2729/// - **Final expression**: `expr` at the very end of a block (no semicolon, becomes
2730///   the block's return value)
2731///
2732/// # Parsing Strategy: Lookahead with `rewind()`
2733///
2734/// The challenge is distinguishing between:
2735/// 1. `{ foo; bar }` - `foo;` is a statement, `bar` is the final expression
2736/// 2. `{ if c { 1 } else { 2 } x }` - the `if` is a statement, `x` is final
2737/// 3. `{ if c { 1 } else { 2 } }` - the `if` IS the final expression
2738///
2739/// We use `rewind()` as a non-consuming lookahead to peek at what follows:
2740///
2741/// - `none_of([RBrace, Semi]).rewind()`: Succeeds if the NEXT token is neither
2742///   `}` nor `;`. The `.rewind()` means we check without consuming the token.
2743///   This identifies control flow in the middle of a block.
2744///
2745/// - `just(RBrace).rewind()`: Succeeds if the NEXT token is `}`. This identifies
2746///   the final expression of a block.
2747///
2748/// # Why `try_map()` for Control Flow?
2749///
2750/// After parsing an expression followed by a non-`}` non-`;` token, we need to
2751/// validate it's actually a control flow expression. If it's something like `x`
2752/// followed by `y`, that's a syntax error (missing semicolon). We use `try_map()`
2753/// to:
2754/// 1. Accept the parse if it's a control flow expression (valid without semicolon)
2755/// 2. Reject it otherwise, allowing chumsky to backtrack and try other branches
2756///
2757/// # Parse Order Matters
2758///
2759/// The `choice()` tries parsers in order. We must try:
2760/// 1. `let_stmt` first (starts with `let` keyword)
2761/// 2. `assign_stmt` second (identifier followed by `.`/`[` chain then `=`)
2762/// 3. `expr_with_semi` (any expression followed by `;`)
2763/// 4. `control_flow_stmt` (control flow NOT followed by `}` - mid-block)
2764/// 5. `final_expr` (any expression followed by `}` - end of block)
2765///
2766/// The assignment parser is tried before general expressions because `x = 5;`
2767/// could otherwise be misparsed as expression `x` followed by unexpected `=`.
2768fn block_item_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, BlockItem>
2769where
2770    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2771{
2772    // Let statement: `let x: T = expr;`
2773    // Always requires a trailing semicolon.
2774    let let_stmt = let_statement_parser(expr.clone()).map(BlockItem::Statement);
2775
2776    // Assignment statement: `target = expr;`
2777    // Target can be: variable (`x`), field (`a.b.c`), or index (`a[i]`).
2778    // Always requires a trailing semicolon.
2779    let assign_stmt = assign_statement_parser(expr.clone()).map(BlockItem::Statement);
2780
2781    // Expression-based item: parse expression ONCE, then decide based on what follows.
2782    // This avoids O(2^n) complexity from repeatedly re-parsing expressions with backtracking.
2783    //
2784    // After parsing an expression, we check the next token:
2785    // - `;` → expression statement (value discarded)
2786    // - `}` → final expression (block's return value)
2787    // - other → mid-block control flow (only valid for if/while/match/loop/break/continue/return)
2788    let expr_item = expr
2789        .then(
2790            choice((
2791                just(TokenKind::Semi).to(ExprFollower::Semi).boxed(),
2792                just(TokenKind::RBrace)
2793                    .rewind()
2794                    .to(ExprFollower::RBrace)
2795                    .boxed(),
2796                any().rewind().to(ExprFollower::Other).boxed(),
2797            ))
2798            .or(end().to(ExprFollower::End)),
2799        )
2800        .try_map(|(e, follower), span| match follower {
2801            ExprFollower::Semi => {
2802                // Expression followed by semicolon: `expr;`
2803                Ok(BlockItem::Statement(Statement::Expr(e)))
2804            }
2805            ExprFollower::RBrace => {
2806                // Final expression: `{ ... expr }`
2807                Ok(BlockItem::Expr(e))
2808            }
2809            ExprFollower::Other | ExprFollower::End => {
2810                // Mid-block control flow (no semicolon, not at end)
2811                // Only control flow expressions are valid here
2812                if is_control_flow_expr(&e) {
2813                    Ok(BlockItem::Statement(Statement::Expr(e)))
2814                } else {
2815                    Err(Rich::custom(span, "expected semicolon after expression"))
2816                }
2817            }
2818        });
2819
2820    // Try parsers in order. Earlier parsers take precedence.
2821    // This order ensures:
2822    // - Keywords (`let`) are matched before being parsed as identifiers
2823    // - Assignments (`x = 5;`) are matched before `x` is parsed as an expression
2824    choice((let_stmt.boxed(), assign_stmt.boxed(), expr_item.boxed())).boxed()
2825}
2826
2827/// Process block items into statements and final expression
2828fn process_block_items(items: Vec<BlockItem>, block_span: Span) -> (Vec<Statement>, Expr) {
2829    let mut statements = Vec::new();
2830    let mut final_expr = None;
2831
2832    for item in items {
2833        match item {
2834            BlockItem::Statement(stmt) => {
2835                // Had a non-semicolon expr before, but now we have more items
2836                // This shouldn't happen with correct grammar, but handle gracefully
2837                if let Some(e) = final_expr.take() {
2838                    statements.push(Statement::Expr(e));
2839                }
2840                statements.push(stmt);
2841            }
2842            BlockItem::Expr(e) => {
2843                if let Some(prev) = final_expr.take() {
2844                    // Had a non-semicolon expr before this one - that's invalid
2845                    // but we'll treat the previous as a statement for error recovery
2846                    statements.push(Statement::Expr(prev));
2847                }
2848                final_expr = Some(e);
2849            }
2850        }
2851    }
2852
2853    let expr = final_expr.unwrap_or_else(|| {
2854        // No explicit final expression. Check if the last statement is a diverging
2855        // expression (break, continue, return) - if so, promote it to the final
2856        // expression since it has type Never which coerces to any type.
2857        if let Some(Statement::Expr(e)) = statements.last()
2858            && is_diverging_expr(e)
2859        {
2860            // Safe to unwrap: we just checked last() is Some(Statement::Expr(_))
2861            let Statement::Expr(e) = statements.pop().unwrap() else {
2862                unreachable!()
2863            };
2864            return e;
2865        }
2866        // Fallback: use a unit expression (block produces unit type)
2867        Expr::Unit(UnitLit {
2868            span: Span::new(block_span.end, block_span.end),
2869        })
2870    });
2871
2872    (statements, expr)
2873}
2874
2875/// Parser for blocks that may end without a final expression (for if/while bodies)
2876fn maybe_unit_block_parser<'src, I>(
2877    expr: GruelParser<'src, I, Expr>,
2878) -> GruelParser<'src, I, BlockExpr>
2879where
2880    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2881{
2882    block_item_parser(expr)
2883        .repeated()
2884        .collect::<Vec<_>>()
2885        .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace))
2886        .map_with(|items, e| {
2887            let span = span_from_extra(e);
2888            let (statements, final_expr) = process_block_items(items, span);
2889            BlockExpr {
2890                statements,
2891                expr: Box::new(final_expr),
2892                span,
2893            }
2894        })
2895        .boxed()
2896}
2897
2898/// Parser for blocks that require a final expression: { statements... expr }
2899fn block_parser<'src, I>(expr: GruelParser<'src, I, Expr>) -> GruelParser<'src, I, Expr>
2900where
2901    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2902{
2903    block_item_parser(expr)
2904        .repeated()
2905        .collect::<Vec<_>>()
2906        .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace))
2907        .map_with(|items, e| {
2908            let span = span_from_extra(e);
2909            let (statements, final_expr) = process_block_items(items, span);
2910            Expr::Block(BlockExpr {
2911                statements,
2912                expr: Box::new(final_expr),
2913                span,
2914            })
2915        })
2916        .boxed()
2917}
2918
2919/// Parser for function definitions: [@directive]* [pub] fn name(params) -> Type { body }
2920///
2921/// ADR-0088 Phase 6 retired the `unchecked` hard keyword in favour
2922/// of the `@mark(unchecked)` directive.
2923fn function_parser<'src, I>() -> GruelParser<'src, I, Function>
2924where
2925    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2926{
2927    let expr = expr_parser();
2928
2929    // Parse optional visibility (pub keyword)
2930    let visibility = just(TokenKind::Pub).or_not().map(|opt| {
2931        if opt.is_some() {
2932            Visibility::Public
2933        } else {
2934            Visibility::Private
2935        }
2936    });
2937
2938    let fn_head: GruelParser<I, (Directives, Visibility)> =
2939        directives_parser().then(visibility).boxed();
2940
2941    let fn_sig: GruelParser<I, (Ident, Vec<Param>)> = just(TokenKind::Fn)
2942        .ignore_then(ident_parser())
2943        .then(params_parser().delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)))
2944        .boxed();
2945
2946    fn_head
2947        .then(fn_sig)
2948        .then(just(TokenKind::Arrow).ignore_then(type_parser()).or_not())
2949        .then(block_parser(expr))
2950        .map_with(
2951            |((((directives, visibility), (name, params)), return_type), body), e| {
2952                let syms = e.state().0.syms;
2953                let is_unchecked = directives_have_mark_unchecked(
2954                    &directives,
2955                    syms.mark_name,
2956                    syms.unchecked_name,
2957                );
2958                Function {
2959                    doc: None,
2960                    directives,
2961                    visibility,
2962                    is_unchecked,
2963                    name,
2964                    params,
2965                    return_type,
2966                    body,
2967                    span: span_from_extra(e),
2968                }
2969            },
2970        )
2971        .boxed()
2972}
2973
2974/// Parser for struct definitions with inline methods:
2975/// [@directive]* [pub] struct Name { field: Type, ... fn method(self) { ... } }
2976///
2977/// Posture (`copy`/`linear`/`affine`) is declared via `@mark(...)` in the
2978/// leading directive list (ADR-0083 Phase 4); there is no head keyword.
2979///
2980/// Fields come first (comma-separated), then methods (no separators needed).
2981fn struct_parser<'src, I>() -> GruelParser<'src, I, StructDecl>
2982where
2983    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
2984{
2985    // Parse optional visibility (pub keyword)
2986    let visibility = just(TokenKind::Pub).or_not().map(|opt| {
2987        if opt.is_some() {
2988            Visibility::Public
2989        } else {
2990            Visibility::Private
2991        }
2992    });
2993
2994    // ADR-0083 Phase 4: posture is declared via `@mark(...)` only;
2995    // the head no longer accepts the keyword form.
2996    let struct_head: GruelParser<I, (Directives, Visibility, Ident)> = directives_parser()
2997        .then(visibility)
2998        .then(just(TokenKind::Struct).ignore_then(ident_parser()))
2999        .map(|((d, v), name)| (d, v, name))
3000        .boxed();
3001
3002    // Box the struct body so DelimitedBy wraps a short Boxed type.
3003    let struct_body: GruelParser<I, (Vec<FieldDecl>, Vec<Method>)> = field_decls_parser()
3004        .then(method_parser().repeated().collect::<Vec<_>>())
3005        .boxed();
3006
3007    struct_head
3008        .then(struct_body.delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)))
3009        .map_with(
3010            |((directives, visibility, name), (fields, methods)), e| StructDecl {
3011                doc: None,
3012                directives,
3013                visibility,
3014                posture: Posture::Affine,
3015                name,
3016                fields,
3017                methods,
3018                span: span_from_extra(e),
3019            },
3020        )
3021        .boxed()
3022}
3023
3024/// Parser for enum variant: unit, tuple `(Type, ...)`, or struct `{ field: Type, ... }`
3025fn enum_variant_parser<'src, I>() -> GruelParser<'src, I, EnumVariant>
3026where
3027    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3028{
3029    use crate::ast::{EnumVariantField, EnumVariantKind};
3030
3031    // Tuple-style fields: (Type, Type, ...)
3032    let tuple_fields = type_parser()
3033        .separated_by(just(TokenKind::Comma))
3034        .allow_trailing()
3035        .collect::<Vec<_>>()
3036        .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen));
3037
3038    // Struct-style fields: { [pub] name: Type, ... }  (ADR-0073)
3039    let variant_field_visibility = just(TokenKind::Pub).or_not().map(|opt| {
3040        if opt.is_some() {
3041            Visibility::Public
3042        } else {
3043            Visibility::Private
3044        }
3045    });
3046    let struct_field = variant_field_visibility
3047        .then(ident_parser())
3048        .then_ignore(just(TokenKind::Colon))
3049        .then(type_parser())
3050        .map_with(|((visibility, name), ty), e| EnumVariantField {
3051            doc: None,
3052            visibility,
3053            name,
3054            ty,
3055            span: span_from_extra(e),
3056        });
3057    let struct_fields = struct_field
3058        .separated_by(just(TokenKind::Comma))
3059        .allow_trailing()
3060        .collect::<Vec<_>>()
3061        .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace));
3062
3063    // Combine: name + optional (tuple | struct) fields
3064    let variant_kind = choice((
3065        tuple_fields.map(EnumVariantKind::Tuple),
3066        struct_fields.map(EnumVariantKind::Struct),
3067    ))
3068    .or_not()
3069    .map(|opt| opt.unwrap_or(EnumVariantKind::Unit));
3070
3071    ident_parser()
3072        .then(variant_kind)
3073        .map_with(|(name, kind), e| EnumVariant {
3074            doc: None,
3075            name,
3076            kind,
3077            span: span_from_extra(e),
3078        })
3079        .boxed()
3080}
3081
3082/// Parser for comma-separated enum variants
3083fn enum_variants_parser<'src, I>() -> GruelParser<'src, I, Vec<EnumVariant>>
3084where
3085    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3086{
3087    enum_variant_parser()
3088        .separated_by(just(TokenKind::Comma))
3089        .allow_trailing()
3090        .collect::<Vec<_>>()
3091        .boxed()
3092}
3093
3094/// Parser for enum definitions: [@directive]* [pub] enum Name { Variant1, Variant2, ... fn method(self) { ... } }
3095///
3096/// Posture (`copy`/`linear`/`affine`) is declared via `@mark(...)` in the
3097/// leading directive list (ADR-0083 Phase 4); there is no head keyword.
3098///
3099/// Variants come first (comma-separated), then methods (no separators needed),
3100/// mirroring the struct body shape.
3101fn enum_parser<'src, I>() -> GruelParser<'src, I, EnumDecl>
3102where
3103    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3104{
3105    // Parse optional visibility (pub keyword)
3106    let visibility = just(TokenKind::Pub).or_not().map(|opt| {
3107        if opt.is_some() {
3108            Visibility::Public
3109        } else {
3110            Visibility::Private
3111        }
3112    });
3113
3114    // ADR-0083 Phase 4: posture is declared via `@mark(...)` only.
3115    let enum_body: GruelParser<I, (Vec<EnumVariant>, Vec<Method>)> = enum_variants_parser()
3116        .then(method_parser().repeated().collect::<Vec<_>>())
3117        .boxed();
3118
3119    directives_parser()
3120        .then(visibility)
3121        .then(just(TokenKind::Enum).ignore_then(ident_parser()))
3122        .then(enum_body.delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)))
3123        .map_with(
3124            |(((directives, visibility), name), (variants, methods)), e| EnumDecl {
3125                doc: None,
3126                directives,
3127                visibility,
3128                posture: Posture::Affine,
3129                name,
3130                variants,
3131                methods,
3132                span: span_from_extra(e),
3133            },
3134        )
3135        .boxed()
3136}
3137
3138/// Parser for method definitions: [@directive]* fn name(self, params) -> Type { body }
3139/// Methods differ from functions in that they can have `self` as the first parameter.
3140fn method_parser<'src, I>() -> GruelParser<'src, I, Method>
3141where
3142    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3143{
3144    let expr = expr_parser();
3145    method_parser_with_expr(expr)
3146}
3147
3148/// Parser for method definitions inside anonymous structs.
3149/// Takes an expression parser as a parameter to avoid creating a new one.
3150fn anon_struct_method_parser<'src, I>(
3151    expr: GruelParser<'src, I, Expr>,
3152) -> GruelParser<'src, I, Method>
3153where
3154    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3155{
3156    method_parser_with_expr(expr)
3157}
3158
3159/// Shared implementation for method parsing.
3160/// Takes an expression parser to allow reuse from different contexts.
3161fn method_parser_with_expr<'src, I>(
3162    expr: GruelParser<'src, I, Expr>,
3163) -> GruelParser<'src, I, Method>
3164where
3165    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3166{
3167    // Parse optional self parameter — `self`, `inout self`, or `borrow self`.
3168    let self_param = self_param_parser();
3169
3170    // Parse self followed by optional regular params
3171    let self_then_params = self_param
3172        .then(
3173            just(TokenKind::Comma)
3174                .ignore_then(params_parser())
3175                .or_not()
3176                .map(|opt| opt.unwrap_or_default()),
3177        )
3178        .map(|(self_param, params)| (Some(self_param), params));
3179
3180    // Parse just regular params (no self) - this is an associated function
3181    let just_params = params_parser().map(|params| (None, params));
3182
3183    // Box params_with_optional_self so DelimitedBy wraps a short type.
3184    let params_with_optional_self: GruelParser<I, (Option<SelfParam>, Vec<Param>)> =
3185        choice((self_then_params.boxed(), just_params.boxed())).boxed();
3186
3187    // Box the method head early to keep subsequent type accumulation short.
3188    // Uses method_name_parser so that `fn __drop(self)` parses as a method named "__drop"
3189    // (ADR-0053 destructor syntax).
3190    //
3191    // ADR-0073: optional `pub` prefix sits between any directives and `fn`.
3192    let method_visibility = just(TokenKind::Pub).or_not().map(|opt| {
3193        if opt.is_some() {
3194            Visibility::Public
3195        } else {
3196            Visibility::Private
3197        }
3198    });
3199    let method_head: GruelParser<I, (Directives, Visibility, Ident)> = directives_parser()
3200        .then(method_visibility)
3201        .then(just(TokenKind::Fn).ignore_then(method_name_parser()))
3202        .map(|((directives, visibility), name)| (directives, visibility, name))
3203        .boxed();
3204
3205    let method_params: GruelParser<I, (Option<SelfParam>, Vec<Param>)> = params_with_optional_self
3206        .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen))
3207        .boxed();
3208
3209    let method_return: GruelParser<I, Option<TypeExpr>> = just(TokenKind::Arrow)
3210        .ignore_then(type_parser())
3211        .or_not()
3212        .boxed();
3213
3214    method_head
3215        .then(method_params)
3216        .then(method_return)
3217        .then(block_parser(expr))
3218        .map_with(
3219            |((((directives, visibility, name), (receiver, params)), return_type), body), e| {
3220                let syms = e.state().0.syms;
3221                let is_unchecked = directives_have_mark_unchecked(
3222                    &directives,
3223                    syms.mark_name,
3224                    syms.unchecked_name,
3225                );
3226                Method {
3227                    doc: None,
3228                    directives,
3229                    visibility,
3230                    is_unchecked,
3231                    name,
3232                    receiver,
3233                    params,
3234                    return_type,
3235                    body,
3236                    span: span_from_extra(e),
3237                }
3238            },
3239        )
3240        .boxed()
3241}
3242
3243/// Parser for const declarations: [pub] const name [: Type] = expr;
3244///
3245/// Used for module re-exports:
3246/// ```gruel
3247/// pub const strings = @import("utils/strings.gruel");
3248/// pub const helper = @import("utils/internal.gruel").helper;
3249/// ```
3250fn const_parser<'src, I>() -> GruelParser<'src, I, ConstDecl>
3251where
3252    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3253{
3254    let expr = expr_parser();
3255
3256    // Parse optional visibility (pub keyword)
3257    let visibility = just(TokenKind::Pub).or_not().map(|opt| {
3258        if opt.is_some() {
3259            Visibility::Public
3260        } else {
3261            Visibility::Private
3262        }
3263    });
3264
3265    // Box after 2 thens to keep accumulated type short.
3266    let const_head: GruelParser<I, (Directives, Visibility, Ident)> = directives_parser()
3267        .then(visibility)
3268        .then(just(TokenKind::Const).ignore_then(ident_parser()))
3269        .map(|((d, v), n)| (d, v, n))
3270        .boxed();
3271
3272    let const_tail: GruelParser<I, (Option<TypeExpr>, Expr)> = just(TokenKind::Colon)
3273        .ignore_then(type_parser())
3274        .or_not()
3275        .then(just(TokenKind::Eq).ignore_then(expr))
3276        .then_ignore(just(TokenKind::Semi))
3277        .boxed();
3278
3279    const_head
3280        .then(const_tail)
3281        .map_with(
3282            |((directives, visibility, name), (ty, init)), e| ConstDecl {
3283                doc: None,
3284                directives,
3285                visibility,
3286                name,
3287                ty,
3288                init: Box::new(init),
3289                span: span_from_extra(e),
3290            },
3291        )
3292        .boxed()
3293}
3294
3295/// Shared parser for method receivers (ADR-0076 sole form).
3296/// Used by methods, interface method signatures, and `drop fn`.
3297///
3298/// Accepts:
3299///   `self`                  → ByValue (annotation elided)
3300///   `self : Self`           → ByValue
3301///   `self : Ref ( Self )`   → Ref (shared borrow receiver)
3302///   `self : MutRef ( Self )` → MutRef (exclusive mutable borrow receiver)
3303fn self_param_parser<'src, I>() -> GruelParser<'src, I, SelfParam>
3304where
3305    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3306{
3307    let typed_byvalue = just(TokenKind::SelfValue)
3308        .then_ignore(just(TokenKind::Colon))
3309        .then_ignore(just(TokenKind::SelfType))
3310        .map_with(|_, e| SelfParam {
3311            kind: SelfReceiverKind::ByValue,
3312            span: span_from_extra(e),
3313        })
3314        .boxed();
3315
3316    // `self : Ref ( Self )` and `self : MutRef ( Self )` need to inspect
3317    // the identifier text since the lexer doesn't tokenize `Ref`/`MutRef`
3318    // distinctly. Match `TokenKind::Ident(spur)` and resolve the spur.
3319    let ref_or_mut_ref = any().try_map_with(
3320        |t: TokenKind, e: &mut chumsky::input::MapExtra<I, ParserExtras<'src>>| match t {
3321            TokenKind::Ident(s) => {
3322                let syms = &e.state().syms;
3323                if s == syms.ref_name {
3324                    Ok(SelfReceiverKind::Ref)
3325                } else if s == syms.mut_ref_name {
3326                    Ok(SelfReceiverKind::MutRef)
3327                } else {
3328                    Err(chumsky::error::Rich::custom(
3329                        e.span(),
3330                        "expected identifier `Ref` or `MutRef`",
3331                    ))
3332                }
3333            }
3334            _ => Err(chumsky::error::Rich::custom(
3335                e.span(),
3336                "expected identifier `Ref` or `MutRef`",
3337            )),
3338        },
3339    );
3340
3341    let typed_ref = just(TokenKind::SelfValue)
3342        .then_ignore(just(TokenKind::Colon))
3343        .ignore_then(ref_or_mut_ref)
3344        .then_ignore(just(TokenKind::LParen))
3345        .then_ignore(just(TokenKind::SelfType))
3346        .then_ignore(just(TokenKind::RParen))
3347        .map_with(|kind, e| SelfParam {
3348            kind,
3349            span: span_from_extra(e),
3350        })
3351        .boxed();
3352
3353    // Bare `self` (no annotation) is ByValue.
3354    let bare_self = just(TokenKind::SelfValue)
3355        .map_with(|_, e| SelfParam {
3356            kind: SelfReceiverKind::ByValue,
3357            span: span_from_extra(e),
3358        })
3359        .boxed();
3360
3361    choice((typed_ref, typed_byvalue, bare_self)).boxed()
3362}
3363
3364/// Parser for top-level items (functions, structs, enums, drop fns, and consts)
3365/// Parser for interface method signatures: `[directives]* fn name([recv] self [, params]) [-> Type];`
3366///
3367/// `recv` is one of `inout` / `borrow`, or omitted for a by-value receiver.
3368/// ADR-0088: signatures accept `@mark(unchecked)` in the directive list.
3369fn interface_method_sig_parser<'src, I>() -> GruelParser<'src, I, MethodSig>
3370where
3371    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3372{
3373    let self_param = self_param_parser();
3374
3375    let extra_params = just(TokenKind::Comma)
3376        .ignore_then(params_parser())
3377        .or_not()
3378        .map(|opt| opt.unwrap_or_default());
3379
3380    let receiver_and_params: GruelParser<I, (SelfParam, Vec<Param>)> = self_param
3381        .then(extra_params)
3382        .delimited_by(just(TokenKind::LParen), just(TokenKind::RParen))
3383        .boxed();
3384
3385    directives_parser()
3386        .then_ignore(just(TokenKind::Fn))
3387        .then(method_name_parser())
3388        .then(receiver_and_params)
3389        .then(just(TokenKind::Arrow).ignore_then(type_parser()).or_not())
3390        .then_ignore(just(TokenKind::Semi))
3391        .map_with(
3392            |(((directives, name), (receiver, params)), return_type), e| {
3393                let syms = e.state().0.syms;
3394                let is_unchecked = directives_have_mark_unchecked(
3395                    &directives,
3396                    syms.mark_name,
3397                    syms.unchecked_name,
3398                );
3399                MethodSig {
3400                    doc: None,
3401                    directives,
3402                    is_unchecked,
3403                    name,
3404                    receiver,
3405                    params,
3406                    return_type,
3407                    span: span_from_extra(e),
3408                }
3409            },
3410        )
3411        .boxed()
3412}
3413
3414/// Parser for interface declarations (ADR-0056):
3415///
3416/// ```text
3417/// [pub] interface Name {
3418///     fn method(self [, params]) [-> RetType];
3419///     ...
3420/// }
3421/// ```
3422fn interface_parser<'src, I>() -> GruelParser<'src, I, InterfaceDecl>
3423where
3424    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3425{
3426    let visibility = just(TokenKind::Pub).or_not().map(|opt| {
3427        if opt.is_some() {
3428            Visibility::Public
3429        } else {
3430            Visibility::Private
3431        }
3432    });
3433
3434    let body = interface_method_sig_parser()
3435        .repeated()
3436        .collect::<Vec<_>>()
3437        .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace))
3438        .boxed();
3439
3440    directives_parser()
3441        .then(visibility)
3442        .then(just(TokenKind::Interface).ignore_then(ident_parser()))
3443        .then(body)
3444        .map_with(
3445            |(((directives, visibility), name), methods), e| InterfaceDecl {
3446                doc: None,
3447                directives,
3448                visibility,
3449                name,
3450                methods,
3451                span: span_from_extra(e),
3452            },
3453        )
3454        .boxed()
3455}
3456
3457/// Parser for derive declarations (ADR-0058):
3458///
3459/// ```text
3460/// derive Name {
3461///     fn method(self [, params]) [-> RetType] { body }
3462///     ...
3463/// }
3464/// ```
3465fn derive_parser<'src, I>() -> GruelParser<'src, I, DeriveDecl>
3466where
3467    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3468{
3469    let body = method_parser()
3470        .repeated()
3471        .collect::<Vec<_>>()
3472        .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace))
3473        .boxed();
3474
3475    just(TokenKind::Derive)
3476        .ignore_then(ident_parser())
3477        .then(body)
3478        .map_with(|(name, methods), e| DeriveDecl {
3479            doc: None,
3480            name,
3481            methods,
3482            span: span_from_extra(e),
3483        })
3484        .boxed()
3485}
3486
3487/// Parser for a single body-less fn declaration inside a `link_extern`
3488/// block (ADR-0085).
3489///
3490/// Shape: `[@directive]* fn name(params) [-> type] ;`
3491///
3492/// Bodies are forbidden here; sema rejects any fn with a body inside a
3493/// `link_extern` block. `pub` / `unchecked` are also rejected — the
3494/// extern scope establishes both.
3495fn extern_fn_parser<'src, I>() -> GruelParser<'src, I, crate::ast::ExternFn>
3496where
3497    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3498{
3499    directives_parser()
3500        .then(just(TokenKind::Fn).ignore_then(ident_parser()))
3501        .then(params_parser().delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)))
3502        .then(just(TokenKind::Arrow).ignore_then(type_parser()).or_not())
3503        .then_ignore(just(TokenKind::Semi))
3504        .map_with(
3505            |(((directives, name), params), return_type), e| crate::ast::ExternFn {
3506                doc: None,
3507                directives,
3508                name,
3509                params,
3510                return_type,
3511                span: span_from_extra(e),
3512            },
3513        )
3514        .boxed()
3515}
3516
3517/// Parser for a `link_extern("libname") { … }` or
3518/// `static_link_extern("libname") { … }` block (ADR-0085 + ADR-0086).
3519fn link_extern_parser<'src, I>() -> GruelParser<'src, I, crate::ast::LinkExternBlock>
3520where
3521    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3522{
3523    let library = select! {
3524        TokenKind::String(s) = e => StringLit {
3525            value: s,
3526            span: span_from_extra(e),
3527        },
3528    }
3529    .boxed();
3530
3531    let mode_keyword = choice((
3532        just(TokenKind::LinkExtern).to(crate::ast::LinkMode::Dynamic),
3533        just(TokenKind::StaticLinkExtern).to(crate::ast::LinkMode::Static),
3534    ))
3535    .boxed();
3536
3537    mode_keyword
3538        .then(library.delimited_by(just(TokenKind::LParen), just(TokenKind::RParen)))
3539        .then(
3540            extern_fn_parser()
3541                .repeated()
3542                .collect::<Vec<_>>()
3543                .delimited_by(just(TokenKind::LBrace), just(TokenKind::RBrace)),
3544        )
3545        .map_with(
3546            |((link_mode, library), items), e| crate::ast::LinkExternBlock {
3547                doc: None,
3548                library,
3549                items,
3550                link_mode,
3551                span: span_from_extra(e),
3552            },
3553        )
3554        .boxed()
3555}
3556
3557fn item_parser<'src, I>() -> GruelParser<'src, I, Item>
3558where
3559    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3560{
3561    choice((
3562        function_parser().map(Item::Function).boxed(),
3563        struct_parser().map(Item::Struct).boxed(),
3564        enum_parser().map(Item::Enum).boxed(),
3565        interface_parser().map(Item::Interface).boxed(),
3566        derive_parser().map(Item::Derive).boxed(),
3567        const_parser().map(Item::Const).boxed(),
3568        link_extern_parser().map(Item::LinkExtern).boxed(),
3569    ))
3570    .boxed()
3571}
3572
3573/// Parser that matches tokens that can start an item (for recovery).
3574/// This is a "lookahead" - it peeks but doesn't consume.
3575fn item_start<'src, I>() -> GruelParser<'src, I, ()>
3576where
3577    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3578{
3579    // NOTE: Split into sub-groups to keep Choice<tuple> symbol length < 4K.
3580    // 9 elements of Boxed<I,(),E> in one tuple wrapped in Rewind would produce ~5K symbols on macOS.
3581    let item_start_a: GruelParser<'src, I, ()> = choice((
3582        just(TokenKind::Fn).ignored().boxed(),
3583        just(TokenKind::Struct).ignored().boxed(),
3584        just(TokenKind::Enum).ignored().boxed(),
3585        just(TokenKind::Interface).ignored().boxed(),
3586        just(TokenKind::Derive).ignored().boxed(),
3587        just(TokenKind::Const).ignored().boxed(),
3588        just(TokenKind::LinkExtern).ignored().boxed(),
3589    ))
3590    .boxed();
3591    let item_start_b: GruelParser<'src, I, ()> = choice((
3592        just(TokenKind::Pub).ignored().boxed(),
3593        just(TokenKind::At).ignored().boxed(), // For @directives
3594    ))
3595    .boxed();
3596    choice((item_start_a, item_start_b))
3597        .rewind() // Peek without consuming
3598        .boxed()
3599}
3600
3601/// Recovery parser that skips tokens until finding an item start.
3602/// Consumes at least one token to guarantee progress, then skips until
3603/// we find a token that could start an item.
3604fn error_recovery<'src, I>() -> GruelParser<'src, I, Item>
3605where
3606    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3607{
3608    // Skip at least one token to make progress, capturing the span
3609    any()
3610        .map_with(|_, extra| extra.span())
3611        // Then skip any more tokens that don't start an item
3612        .then(
3613            any()
3614                .and_is(item_start().not())
3615                .repeated()
3616                .collect::<Vec<_>>(),
3617        )
3618        .map(|(start_span, _): (SimpleSpan, Vec<TokenKind>)| {
3619            // Convert SimpleSpan to Span
3620            Item::Error(to_gruel_util(start_span))
3621        })
3622        .boxed()
3623}
3624
3625/// Parser for top-level items with error recovery.
3626/// When an item fails to parse, we skip tokens until we find the start of
3627/// another item, emit an Error node, and continue parsing.
3628fn item_with_recovery<'src, I>() -> GruelParser<'src, I, Item>
3629where
3630    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3631{
3632    item_parser()
3633        .recover_with(via_parser(error_recovery()))
3634        .boxed()
3635}
3636
3637/// Main parser that produces an AST
3638fn ast_parser<'src, I>() -> GruelParser<'src, I, Ast>
3639where
3640    I: ValueInput<'src, Token = TokenKind, Span = SimpleSpan>,
3641{
3642    item_with_recovery()
3643        .repeated()
3644        .collect::<Vec<_>>()
3645        .then_ignore(end())
3646        .map(|items| Ast {
3647            module_doc: None,
3648            items,
3649        })
3650        .boxed()
3651}
3652
3653/// Format a RichPattern for display in error messages.
3654fn format_pattern(pattern: &chumsky::error::RichPattern<'_, TokenKind>) -> String {
3655    use chumsky::error::RichPattern;
3656    match pattern {
3657        RichPattern::Token(tok) => tok.name().to_string(),
3658        RichPattern::Label(label) => label.to_string(),
3659        RichPattern::Identifier(ident) => format!("'{}'", ident),
3660        RichPattern::Any => "any token".to_string(),
3661        RichPattern::SomethingElse => "something else".to_string(),
3662        RichPattern::EndOfInput => "end of input".to_string(),
3663    }
3664}
3665
3666/// Convert chumsky Rich error to CompileError, preserving rich context.
3667fn convert_error(err: Rich<'_, TokenKind>) -> CompileError {
3668    let span = to_gruel_util(*err.span());
3669
3670    // Build the base error from the reason
3671    let mut error = match err.reason() {
3672        chumsky::error::RichReason::ExpectedFound { expected, found } => {
3673            let expected_str: Cow<'static, str> = if expected.is_empty() {
3674                Cow::Borrowed("something")
3675            } else {
3676                Cow::Owned(
3677                    expected
3678                        .iter()
3679                        .take(3) // Limit to first 3 for readability
3680                        .map(format_pattern)
3681                        .collect::<Vec<_>>()
3682                        .join(" or "),
3683                )
3684            };
3685
3686            let found_str: Cow<'static, str> = match found.as_ref() {
3687                Some(t) => Cow::Owned(t.name().to_string()),
3688                None => Cow::Borrowed("end of file"),
3689            };
3690
3691            CompileError::new(
3692                ErrorKind::UnexpectedToken {
3693                    expected: expected_str,
3694                    found: found_str,
3695                },
3696                span,
3697            )
3698        }
3699        chumsky::error::RichReason::Custom(msg) => {
3700            // Preserve the custom error message directly
3701            CompileError::new(ErrorKind::ParseError(msg.clone()), span)
3702        }
3703    };
3704
3705    // Add labelled contexts as secondary labels
3706    for (pattern, ctx_span) in err.contexts() {
3707        let label_msg = format!("while parsing {}", format_pattern(pattern));
3708        let label_span = to_gruel_util(*ctx_span);
3709        error = error.with_label(label_msg, label_span);
3710    }
3711
3712    error
3713}
3714
3715/// Chumsky-based parser that converts tokens into an AST.
3716pub struct ChumskyParser {
3717    tokens: Vec<(TokenKind, SimpleSpan)>,
3718    /// ADR-0089: the original token stream (including `LineDoc`s) used to
3719    /// derive doc blocks when the `docs` preview feature is enabled.
3720    raw_tokens: Vec<gruel_lexer::Token>,
3721    source_len: usize,
3722    interner: ThreadedRodeo,
3723    /// File ID for spans in this file.
3724    file_id: FileId,
3725    /// Preview features enabled for this parse (ADR-0005, ADR-0049).
3726    ///
3727    /// The parser always accepts the full grammar (including preview syntax); a
3728    /// post-parse validation pass emits errors if preview-only syntax is used
3729    /// without the corresponding flag.
3730    preview_features: PreviewFeatures,
3731    /// ADR-0089: source text of the file being parsed. Required to compute
3732    /// line numbers for doc-block grouping when `docs` preview is enabled.
3733    source: Option<String>,
3734}
3735
3736impl ChumskyParser {
3737    /// Create a new parser from tokens and an interner produced by the lexer.
3738    pub fn new(tokens: Vec<gruel_lexer::Token>, interner: ThreadedRodeo) -> Self {
3739        let source_len = tokens.last().map(|t| t.span.end as usize).unwrap_or(0);
3740        // Extract file_id from the first token (all tokens in a file have the same file_id)
3741        let file_id = tokens
3742            .first()
3743            .map(|t| t.span.file_id)
3744            .unwrap_or(FileId::DEFAULT);
3745
3746        let raw_tokens = tokens.clone();
3747        let spanned_tokens: Vec<(TokenKind, SimpleSpan)> = tokens
3748            .into_iter()
3749            .filter(|t| t.kind != TokenKind::Eof) // Remove EOF, chumsky handles end differently
3750            // ADR-0089: drop `LineDoc` tokens before chumsky sees them.
3751            // The Phase 2 post-pass uses `raw_tokens` to recover them.
3752            .filter(|t| !matches!(t.kind, TokenKind::LineDoc(_)))
3753            .map(|t| {
3754                (
3755                    t.kind,
3756                    SimpleSpan::new(t.span.start as usize, t.span.end as usize),
3757                )
3758            })
3759            .collect();
3760        Self {
3761            tokens: spanned_tokens,
3762            raw_tokens,
3763            source_len,
3764            interner,
3765            file_id,
3766            preview_features: PreviewFeatures::default(),
3767            source: None,
3768        }
3769    }
3770
3771    /// Set the preview features enabled for this parse. Required to use any
3772    /// syntax gated behind a `--preview` flag (ADR-0005).
3773    pub fn with_preview_features(mut self, features: PreviewFeatures) -> Self {
3774        self.preview_features = features;
3775        self
3776    }
3777
3778    /// ADR-0089: attach the source text. Required to derive doc blocks
3779    /// when the `docs` preview feature is enabled. Without it, `LineDoc`
3780    /// tokens are silently dropped (same as the Phase 1 behavior).
3781    pub fn with_source(mut self, source: impl Into<String>) -> Self {
3782        self.source = Some(source.into());
3783        self
3784    }
3785
3786    /// Parse the tokens into an AST, returning the AST and the interner.
3787    ///
3788    /// Returns all parse errors if parsing fails, not just the first one.
3789    pub fn parse(mut self) -> MultiErrorResult<(Ast, ThreadedRodeo)> {
3790        // Pre-intern primitive type symbols and create parser state with file ID
3791        let syms = PrimitiveTypeSpurs::new(&mut self.interner);
3792        let parser_state = ParserState::new(syms, self.file_id);
3793        let mut state = SimpleState(parser_state);
3794
3795        // Create a stream from the token iterator
3796        let token_iter = self.tokens.iter().cloned();
3797        let stream = Stream::from_iter(token_iter);
3798
3799        // Map the stream to split (Token, Span) tuples
3800        let eoi: SimpleSpan = (self.source_len..self.source_len).into();
3801        let mapped = stream.map(eoi, |(tok, span)| (tok, span));
3802
3803        let mut ast = ast_parser()
3804            .parse_with_state(mapped, &mut state)
3805            .into_result()
3806            .map_err(|errs| {
3807                let errors: Vec<CompileError> = errs.into_iter().map(convert_error).collect();
3808                CompileErrors::from(errors)
3809            })?;
3810
3811        // Post-parse AST validation: refutability in let-bindings
3812        // (unconditional per ADR-0049 §2). The nested-patterns preview
3813        // gate was removed at stabilization (Phase 8).
3814        let mut errors = Vec::new();
3815        let validator = AstValidator;
3816        validator.walk_ast(&ast, &mut errors);
3817        if !errors.is_empty() {
3818            return Err(CompileErrors::from(errors));
3819        }
3820
3821        // ADR-0089 (stable): always attach docs when source is provided.
3822        // Call sites that pre-date this and don't pass `with_source()`
3823        // simply have no docs on their AST (same as the Phase 1 drop).
3824        if let Some(ref source) = self.source {
3825            let mut doc_errors = Vec::new();
3826            attach_docs(
3827                &mut ast,
3828                &self.raw_tokens,
3829                source,
3830                self.file_id,
3831                &self.interner,
3832                &mut doc_errors,
3833            );
3834            if !doc_errors.is_empty() {
3835                return Err(CompileErrors::from(doc_errors));
3836            }
3837        }
3838
3839        Ok((ast, self.interner))
3840    }
3841}
3842
3843/// ADR-0089: a contiguous run of `///` lines forming a single doc block.
3844#[derive(Debug, Clone)]
3845struct DocBlock {
3846    /// Joined body text (`\n`-separated, with `///` and one leading space
3847    /// stripped per line).
3848    body: String,
3849    /// Source span covering the block.
3850    span: Span,
3851    /// 1-based start line.
3852    start_line: usize,
3853    /// 1-based end line (inclusive).
3854    end_line: usize,
3855}
3856
3857/// ADR-0089: group consecutive `LineDoc` tokens into doc blocks.
3858///
3859/// A doc block is a run of `///` lines on adjacent source lines (no blank
3860/// line, no non-doc token between). Two LineDoc tokens belong to the same
3861/// block iff their line numbers differ by exactly one.
3862fn collect_doc_blocks(
3863    raw_tokens: &[gruel_lexer::Token],
3864    line_index: &LineIndex,
3865    interner: &ThreadedRodeo,
3866) -> Vec<DocBlock> {
3867    let mut blocks: Vec<DocBlock> = Vec::new();
3868    let mut current_lines: Vec<&str> = Vec::new();
3869    let mut current_span: Option<Span> = None;
3870    let mut current_start_line: usize = 0;
3871    let mut current_end_line: usize = 0;
3872    let mut last_was_doc = false;
3873
3874    for tok in raw_tokens {
3875        match tok.kind {
3876            TokenKind::LineDoc(spur) => {
3877                let line = line_index.span_line_number(tok.span);
3878                let body = interner.resolve(&spur);
3879                if last_was_doc && line == current_end_line + 1 {
3880                    current_lines.push(body);
3881                    current_end_line = line;
3882                    current_span = current_span.map(|s| Span::cover(s, tok.span));
3883                } else {
3884                    if let (Some(span), false) = (current_span, current_lines.is_empty()) {
3885                        blocks.push(DocBlock {
3886                            body: current_lines.join("\n"),
3887                            span,
3888                            start_line: current_start_line,
3889                            end_line: current_end_line,
3890                        });
3891                    }
3892                    current_lines = vec![body];
3893                    current_span = Some(tok.span);
3894                    current_start_line = line;
3895                    current_end_line = line;
3896                }
3897                last_was_doc = true;
3898            }
3899            TokenKind::Eof => {}
3900            _ => {
3901                if let (Some(span), false) = (current_span, current_lines.is_empty()) {
3902                    blocks.push(DocBlock {
3903                        body: current_lines.join("\n"),
3904                        span,
3905                        start_line: current_start_line,
3906                        end_line: current_end_line,
3907                    });
3908                    current_lines = Vec::new();
3909                    current_span = None;
3910                }
3911                last_was_doc = false;
3912            }
3913        }
3914    }
3915    if let (Some(span), false) = (current_span, current_lines.is_empty()) {
3916        blocks.push(DocBlock {
3917            body: current_lines.join("\n"),
3918            span,
3919            start_line: current_start_line,
3920            end_line: current_end_line,
3921        });
3922    }
3923    blocks
3924}
3925
3926/// ADR-0089: apply the attachment rule and populate `Ast.module_doc` plus
3927/// each item's `doc` field. Emits parse errors for any doc block that
3928/// neither qualifies as the module candidate nor is glued to an item.
3929fn attach_docs(
3930    ast: &mut Ast,
3931    raw_tokens: &[gruel_lexer::Token],
3932    source: &str,
3933    file_id: FileId,
3934    interner: &ThreadedRodeo,
3935    errors: &mut Vec<CompileError>,
3936) {
3937    let line_index = LineIndex::new(source);
3938    let blocks = collect_doc_blocks(raw_tokens, &line_index, interner);
3939    if blocks.is_empty() {
3940        return;
3941    }
3942
3943    // Compute, for each non-LineDoc token: its start line. The "first
3944    // non-doc token line" tells us whether a doc block has an item above
3945    // it (file-level rule).
3946    let first_non_doc_line: Option<usize> = raw_tokens
3947        .iter()
3948        .find(|t| !matches!(t.kind, TokenKind::LineDoc(_) | TokenKind::Eof))
3949        .map(|t| line_index.span_line_number(t.span));
3950
3951    // Build a lookup: byte offset → 1-based line for every non-doc, non-EOF
3952    // token. Used to recognise when a doc block is glued to a follower.
3953    let mut token_line_by_start: HashMap<u32, usize> = HashMap::new();
3954    for tok in raw_tokens {
3955        if matches!(tok.kind, TokenKind::LineDoc(_) | TokenKind::Eof) {
3956            continue;
3957        }
3958        token_line_by_start.insert(tok.span.start, line_index.span_line_number(tok.span));
3959    }
3960
3961    // Walk blocks in order. For each block, decide attachment.
3962    // - Module candidate: first block in file AND no non-doc token appears
3963    //   on a line strictly before the block's start_line.
3964    // - Glued: there exists a non-doc token whose start line == end_line + 1.
3965    let mut module_doc_assigned = false;
3966    // For glued blocks: map item start byte offset → Doc.
3967    let mut item_docs: HashMap<u32, Doc> = HashMap::new();
3968
3969    for (idx, block) in blocks.iter().enumerate() {
3970        let is_first_block = idx == 0;
3971        let no_item_above = match first_non_doc_line {
3972            None => true,
3973            Some(line) => line >= block.start_line,
3974        };
3975        let qualifies_as_module = is_first_block && no_item_above;
3976
3977        // Find the next non-doc token after this block (the immediately
3978        // following item-candidate).
3979        let next_non_doc = raw_tokens.iter().find(|t| {
3980            !matches!(t.kind, TokenKind::LineDoc(_) | TokenKind::Eof)
3981                && t.span.start > block.span.end
3982        });
3983        let glued = next_non_doc
3984            .map(|t| line_index.span_line_number(t.span) == block.end_line + 1)
3985            .unwrap_or(false);
3986
3987        let doc = Doc {
3988            body: block.body.clone(),
3989            span: with_file(block.span, file_id),
3990        };
3991
3992        if qualifies_as_module && !glued {
3993            // Module candidate with a blank-line separator → module doc.
3994            if !module_doc_assigned {
3995                ast.module_doc = Some(doc);
3996                module_doc_assigned = true;
3997            }
3998            continue;
3999        }
4000
4001        if glued {
4002            // Attach to the immediately following item by its start position.
4003            // The post-walk over the AST will pick it up.
4004            let target = next_non_doc.expect("glued implies next token exists");
4005            item_docs.insert(target.span.start, doc);
4006            continue;
4007        }
4008
4009        // Not glued, not module candidate → parse error.
4010        let span_in_file = with_file(block.span, file_id);
4011        errors.push(CompileError::new(
4012            ErrorKind::ParseError(
4013                "doc comment must be immediately followed by an item".to_string(),
4014            ),
4015            span_in_file,
4016        ));
4017    }
4018
4019    // Distribute item_docs onto matching AST items.
4020    if !item_docs.is_empty() {
4021        distribute_docs_to_items(ast, &mut item_docs);
4022    }
4023}
4024
4025/// Helper: set a `Span`'s file id when constructing a new span from one
4026/// that already carries the right id (no-op in practice).
4027fn with_file(span: Span, file_id: FileId) -> Span {
4028    Span::with_file(file_id, span.start, span.end)
4029}
4030
4031/// ADR-0089: walk the AST and attach docs to items based on byte-offset
4032/// matching against `item_docs`. Visits every doc-bearing position:
4033/// top-level items plus nested method, field, and variant declarations.
4034fn distribute_docs_to_items(ast: &mut Ast, item_docs: &mut HashMap<u32, Doc>) {
4035    for item in &mut ast.items {
4036        match item {
4037            Item::Function(f) => {
4038                attach_if_match(&mut f.doc, item_docs, f.span.start);
4039            }
4040            Item::Struct(s) => {
4041                attach_if_match(&mut s.doc, item_docs, s.span.start);
4042                for field in &mut s.fields {
4043                    attach_if_match(&mut field.doc, item_docs, field.span.start);
4044                }
4045                for method in &mut s.methods {
4046                    attach_if_match(&mut method.doc, item_docs, method.span.start);
4047                }
4048            }
4049            Item::Enum(e) => {
4050                attach_if_match(&mut e.doc, item_docs, e.span.start);
4051                for variant in &mut e.variants {
4052                    attach_if_match(&mut variant.doc, item_docs, variant.span.start);
4053                    if let crate::ast::EnumVariantKind::Struct(fields) = &mut variant.kind {
4054                        for field in fields {
4055                            attach_if_match(&mut field.doc, item_docs, field.span.start);
4056                        }
4057                    }
4058                }
4059                for method in &mut e.methods {
4060                    attach_if_match(&mut method.doc, item_docs, method.span.start);
4061                }
4062            }
4063            Item::Interface(i) => {
4064                attach_if_match(&mut i.doc, item_docs, i.span.start);
4065                for sig in &mut i.methods {
4066                    attach_if_match(&mut sig.doc, item_docs, sig.span.start);
4067                }
4068            }
4069            Item::Derive(d) => {
4070                attach_if_match(&mut d.doc, item_docs, d.span.start);
4071                for method in &mut d.methods {
4072                    attach_if_match(&mut method.doc, item_docs, method.span.start);
4073                }
4074            }
4075            Item::Const(c) => {
4076                attach_if_match(&mut c.doc, item_docs, c.span.start);
4077            }
4078            Item::LinkExtern(b) => {
4079                attach_if_match(&mut b.doc, item_docs, b.span.start);
4080                for fn_decl in &mut b.items {
4081                    attach_if_match(&mut fn_decl.doc, item_docs, fn_decl.span.start);
4082                }
4083            }
4084            Item::Error(_) => {}
4085        }
4086    }
4087}
4088
4089fn attach_if_match(slot: &mut Option<Doc>, item_docs: &mut HashMap<u32, Doc>, key: u32) {
4090    if let Some(doc) = item_docs.remove(&key) {
4091        *slot = Some(doc);
4092    }
4093}
4094
4095/// Post-parse AST validator for patterns (ADR-0049): rejects refutable
4096/// patterns in `let` bindings. Formerly also gated nested syntax behind
4097/// the `nested_patterns` preview feature — that gate was removed when
4098/// ADR-0049 stabilized (Phase 8).
4099struct AstValidator;
4100
4101impl AstValidator {
4102    fn walk_ast(&self, ast: &Ast, errors: &mut Vec<CompileError>) {
4103        for item in &ast.items {
4104            validate_item(item, errors, self);
4105        }
4106    }
4107}
4108
4109/// Classify whether a pattern is refutable. Refutable = matches only some
4110/// values of its inferred type; irrefutable = matches every value.
4111fn is_refutable(pat: &Pattern) -> bool {
4112    match pat {
4113        Pattern::Wildcard(_) => false,
4114        Pattern::Ident { .. } => false,
4115        Pattern::Int(_) | Pattern::NegInt(_) | Pattern::Bool(_) => true,
4116        Pattern::Path(_) => true,
4117        // A variant pattern can't be irrefutable without type info (we'd need
4118        // to know whether it's a single-variant enum). Conservatively refutable.
4119        Pattern::DataVariant { .. } | Pattern::StructVariant { .. } => true,
4120        Pattern::Struct { fields, .. } => fields.iter().any(|f| {
4121            // `..` and shorthand bindings are irrefutable by themselves.
4122            f.sub.as_ref().is_some_and(is_refutable)
4123        }),
4124        Pattern::Tuple { elems, .. } => elems.iter().any(|e| match e {
4125            TupleElemPattern::Pattern(p) => is_refutable(p),
4126            TupleElemPattern::Rest(_) => false,
4127        }),
4128        // ADR-0079 Phase 3: an unroll-arm template's expanded pattern
4129        // is variant-shaped, hence refutable.
4130        Pattern::ComptimeUnrollArm { .. } => true,
4131    }
4132}
4133
4134fn validate_item(item: &Item, errors: &mut Vec<CompileError>, v: &AstValidator) {
4135    match item {
4136        Item::Function(f) => validate_expr(&f.body, errors, v),
4137        Item::Struct(s) => {
4138            for m in &s.methods {
4139                validate_expr(&m.body, errors, v);
4140            }
4141        }
4142        Item::Enum(e) => {
4143            for m in &e.methods {
4144                validate_expr(&m.body, errors, v);
4145            }
4146        }
4147        Item::Const(c) => validate_expr(&c.init, errors, v),
4148        Item::Interface(_) => {}
4149        Item::Derive(d) => {
4150            for m in &d.methods {
4151                validate_expr(&m.body, errors, v);
4152            }
4153        }
4154        Item::LinkExtern(_) => {
4155            // Body-less extern declarations have no expressions to validate.
4156        }
4157        Item::Error(_) => {}
4158    }
4159}
4160
4161fn validate_block(block: &crate::ast::BlockExpr, errors: &mut Vec<CompileError>, v: &AstValidator) {
4162    use crate::ast::AssignTarget;
4163    for stmt in &block.statements {
4164        match stmt {
4165            Statement::Let(l) => {
4166                validate_let_pattern(&l.pattern, errors, v);
4167                validate_expr(&l.init, errors, v);
4168            }
4169            Statement::Assign(a) => {
4170                validate_expr(&a.value, errors, v);
4171                match &a.target {
4172                    AssignTarget::Var(_) => {}
4173                    AssignTarget::Field(f) => validate_expr(&f.base, errors, v),
4174                    AssignTarget::Index(i) => {
4175                        validate_expr(&i.base, errors, v);
4176                        validate_expr(&i.index, errors, v);
4177                    }
4178                }
4179            }
4180            Statement::Expr(e) => validate_expr(e, errors, v),
4181        }
4182    }
4183    validate_expr(&block.expr, errors, v);
4184}
4185
4186fn validate_expr(expr: &Expr, errors: &mut Vec<CompileError>, v: &AstValidator) {
4187    use crate::ast::IntrinsicArg;
4188    match expr {
4189        Expr::Block(b) => validate_block(b, errors, v),
4190        Expr::Match(m) => {
4191            validate_expr(&m.scrutinee, errors, v);
4192            for arm in &m.arms {
4193                validate_match_pattern(&arm.pattern, errors, v);
4194                validate_expr(&arm.body, errors, v);
4195            }
4196        }
4197        Expr::If(i) => {
4198            validate_expr(&i.cond, errors, v);
4199            validate_block(&i.then_block, errors, v);
4200            if let Some(e) = &i.else_block {
4201                validate_block(e, errors, v);
4202            }
4203        }
4204        Expr::While(w) => {
4205            validate_expr(&w.cond, errors, v);
4206            validate_block(&w.body, errors, v);
4207        }
4208        Expr::For(f) => {
4209            validate_expr(&f.iterable, errors, v);
4210            validate_block(&f.body, errors, v);
4211        }
4212        Expr::Loop(l) => validate_block(&l.body, errors, v),
4213        Expr::Binary(b) => {
4214            validate_expr(&b.left, errors, v);
4215            validate_expr(&b.right, errors, v);
4216        }
4217        Expr::Unary(u) => validate_expr(&u.operand, errors, v),
4218        Expr::Call(c) => {
4219            for arg in &c.args {
4220                validate_expr(&arg.expr, errors, v);
4221            }
4222        }
4223        Expr::IntrinsicCall(i) => {
4224            for arg in &i.args {
4225                if let IntrinsicArg::Expr(e) = arg {
4226                    validate_expr(e, errors, v);
4227                }
4228            }
4229        }
4230        Expr::MethodCall(m) => {
4231            validate_expr(&m.receiver, errors, v);
4232            for arg in &m.args {
4233                validate_expr(&arg.expr, errors, v);
4234            }
4235        }
4236        Expr::AssocFnCall(a) => {
4237            for arg in &a.args {
4238                validate_expr(&arg.expr, errors, v);
4239            }
4240        }
4241        Expr::Field(f) => validate_expr(&f.base, errors, v),
4242        Expr::TupleIndex(t) => validate_expr(&t.base, errors, v),
4243        Expr::Index(i) => {
4244            validate_expr(&i.base, errors, v);
4245            validate_expr(&i.index, errors, v);
4246        }
4247        Expr::Return(r) => {
4248            if let Some(val) = &r.value {
4249                validate_expr(val, errors, v);
4250            }
4251        }
4252        Expr::Paren(p) => validate_expr(&p.inner, errors, v),
4253        Expr::StructLit(s) => {
4254            for f in &s.fields {
4255                validate_expr(&f.value, errors, v);
4256            }
4257        }
4258        Expr::EnumStructLit(s) => {
4259            for f in &s.fields {
4260                validate_expr(&f.value, errors, v);
4261            }
4262        }
4263        Expr::ArrayLit(a) => {
4264            for e in &a.elements {
4265                validate_expr(e, errors, v);
4266            }
4267        }
4268        Expr::Tuple(t) => {
4269            for e in &t.elems {
4270                validate_expr(e, errors, v);
4271            }
4272        }
4273        Expr::Comptime(c) => validate_expr(&c.expr, errors, v),
4274        Expr::AnonFn(a) => validate_block(&a.body, errors, v),
4275        Expr::Range(r) => {
4276            if let Some(lo) = &r.lo {
4277                validate_expr(lo, errors, v);
4278            }
4279            if let Some(hi) = &r.hi {
4280                validate_expr(hi, errors, v);
4281            }
4282        }
4283        Expr::ComptimeUnrollFor(_) | Expr::Checked(_) => {}
4284        Expr::TypeLit(_)
4285        | Expr::Int(_)
4286        | Expr::Float(_)
4287        | Expr::String(_)
4288        | Expr::Char(_)
4289        | Expr::Bool(_)
4290        | Expr::Unit(_)
4291        | Expr::Ident(_)
4292        | Expr::Path(_)
4293        | Expr::SelfExpr(_)
4294        | Expr::Break(_)
4295        | Expr::Continue(_)
4296        | Expr::Error(_) => {}
4297    }
4298}
4299
4300/// Validate a pattern in a let-binding position.
4301///
4302/// 1. If the pattern is refutable, emit a `RefutablePatternInLet` error at the
4303///    refutable sub-pattern's span. This rule applies unconditionally — it's
4304///    a property of the let context, not a preview-gated feature.
4305/// 2. If the preview feature is off, emit preview-required errors for nested
4306///    sub-patterns and `..` rest shapes.
4307fn validate_let_pattern(pat: &Pattern, errors: &mut Vec<CompileError>, _v: &AstValidator) {
4308    if is_refutable(pat) {
4309        errors.push(CompileError::new(
4310            ErrorKind::RefutablePatternInLet,
4311            refutable_focus_span(pat),
4312        ));
4313    }
4314}
4315
4316/// Validate a pattern in a match-arm position. Formerly preview-gated;
4317/// now a no-op since ADR-0049 is stabilized.
4318fn validate_match_pattern(_pat: &Pattern, _errors: &mut Vec<CompileError>, _v: &AstValidator) {}
4319
4320/// Pick the most informative span for a refutability error: the innermost
4321/// refutable sub-pattern when we can find one, otherwise the pattern's own span.
4322fn refutable_focus_span(pat: &Pattern) -> Span {
4323    match pat {
4324        Pattern::Int(l) => l.span,
4325        Pattern::NegInt(l) => l.span,
4326        Pattern::Bool(l) => l.span,
4327        Pattern::Path(p) => p.span,
4328        Pattern::DataVariant { span, .. } | Pattern::StructVariant { span, .. } => *span,
4329        Pattern::Struct { fields, .. } => fields
4330            .iter()
4331            .filter_map(|f| f.sub.as_ref())
4332            .find(|sub| is_refutable(sub))
4333            .map(refutable_focus_span)
4334            .unwrap_or(pat.span()),
4335        Pattern::Tuple { elems, .. } => elems
4336            .iter()
4337            .filter_map(|e| match e {
4338                TupleElemPattern::Pattern(p) if is_refutable(p) => Some(p),
4339                _ => None,
4340            })
4341            .next()
4342            .map(refutable_focus_span)
4343            .unwrap_or(pat.span()),
4344        Pattern::Wildcard(_) | Pattern::Ident { .. } => pat.span(),
4345        Pattern::ComptimeUnrollArm { span, .. } => *span,
4346    }
4347}
4348
4349// The former `check_let_pattern_preview`, `check_flat_field_pattern`,
4350// `check_flat_tuple_elem`, `check_match_pattern_preview`, and
4351// `preview_required_err` helpers were removed when ADR-0049 stabilized
4352// (Phase 8). Nested patterns are now always accepted by the parser;
4353// refutability in `let` remains enforced by `validate_let_pattern`.
4354
4355#[cfg(test)]
4356mod tests {
4357    use super::*;
4358    use gruel_lexer::Lexer;
4359
4360    /// Result type for parsing that includes both the AST and interner.
4361    /// Provides convenient access to both the parsed AST and symbol resolution.
4362    #[derive(Debug)]
4363    struct ParseResult {
4364        ast: Ast,
4365        interner: ThreadedRodeo,
4366    }
4367
4368    impl ParseResult {
4369        /// Get the string for a symbol.
4370        fn get(&self, sym: Spur) -> &str {
4371            self.interner.resolve(&sym)
4372        }
4373    }
4374
4375    /// Result type for expression parsing that includes both the expr and interner.
4376    #[derive(Debug)]
4377    struct ExprResult {
4378        expr: Expr,
4379        interner: ThreadedRodeo,
4380    }
4381
4382    impl ExprResult {
4383        /// Get the string for a symbol.
4384        fn get(&self, sym: Spur) -> &str {
4385            self.interner.resolve(&sym)
4386        }
4387    }
4388
4389    fn parse(source: &str) -> MultiErrorResult<ParseResult> {
4390        let lexer = Lexer::new(source);
4391        let (tokens, interner) = lexer.tokenize().map_err(CompileErrors::from)?;
4392        let parser = ChumskyParser::new(tokens, interner);
4393        let (ast, interner) = parser.parse()?;
4394        Ok(ParseResult { ast, interner })
4395    }
4396
4397    fn parse_expr(source: &str) -> MultiErrorResult<ExprResult> {
4398        let result = parse(&format!("fn main() -> i32 {{ {} }}", source))?;
4399        let interner = result.interner;
4400        let expr = match result.ast.items.into_iter().next().unwrap() {
4401            Item::Function(f) => match f.body {
4402                Expr::Block(block) => *block.expr,
4403                other => other,
4404            },
4405            Item::Struct(_) => panic!("parse_expr helper should only be used with functions"),
4406            Item::Enum(_) => panic!("parse_expr helper should only be used with functions"),
4407            Item::Interface(_) => panic!("parse_expr helper should only be used with functions"),
4408            Item::Derive(_) => panic!("parse_expr helper should only be used with functions"),
4409            Item::Const(_) => panic!("parse_expr helper should only be used with functions"),
4410            Item::LinkExtern(_) => {
4411                panic!("parse_expr helper should only be used with functions")
4412            }
4413            Item::Error(_) => panic!("parse_expr helper should only be used with functions"),
4414        };
4415        Ok(ExprResult { expr, interner })
4416    }
4417
4418    #[test]
4419    fn test_chumsky_parse_main() {
4420        let result = parse("fn main() -> i32 { 42 }").unwrap();
4421
4422        assert_eq!(result.ast.items.len(), 1);
4423        match &result.ast.items[0] {
4424            Item::Function(f) => {
4425                assert_eq!(result.get(f.name.name), "main");
4426                match f.return_type.as_ref().unwrap() {
4427                    TypeExpr::Named(ident) => assert_eq!(result.get(ident.name), "i32"),
4428                    _ => panic!("expected Named type"),
4429                }
4430                match &f.body {
4431                    Expr::Block(block) => match block.expr.as_ref() {
4432                        Expr::Int(lit) => assert_eq!(lit.value, 42),
4433                        _ => panic!("expected Int"),
4434                    },
4435                    _ => panic!("expected Block"),
4436                }
4437            }
4438            Item::Struct(_) => panic!("expected Function"),
4439            Item::Enum(_) => panic!("expected Function"),
4440            Item::Interface(_) => panic!("expected Function"),
4441            Item::Derive(_) => panic!("expected Function"),
4442            Item::Const(_) => panic!("expected Function"),
4443            Item::LinkExtern(_) => panic!("expected Function"),
4444            Item::Error(_) => panic!("expected Function"),
4445        }
4446    }
4447
4448    #[test]
4449    fn test_chumsky_parse_addition() {
4450        let result = parse_expr("1 + 2").unwrap();
4451        match result.expr {
4452            Expr::Binary(bin) => {
4453                assert!(matches!(bin.op, BinaryOp::Add));
4454                match (*bin.left, *bin.right) {
4455                    (Expr::Int(l), Expr::Int(r)) => {
4456                        assert_eq!(l.value, 1);
4457                        assert_eq!(r.value, 2);
4458                    }
4459                    _ => panic!("expected Int operands"),
4460                }
4461            }
4462            _ => panic!("expected Binary"),
4463        }
4464    }
4465
4466    #[test]
4467    fn test_chumsky_parse_precedence() {
4468        // 1 + 2 * 3 should parse as 1 + (2 * 3)
4469        let result = parse_expr("1 + 2 * 3").unwrap();
4470        match result.expr {
4471            Expr::Binary(bin) => {
4472                assert!(matches!(bin.op, BinaryOp::Add));
4473                match *bin.left {
4474                    Expr::Int(l) => assert_eq!(l.value, 1),
4475                    _ => panic!("expected Int"),
4476                }
4477                match *bin.right {
4478                    Expr::Binary(inner) => {
4479                        assert!(matches!(inner.op, BinaryOp::Mul));
4480                    }
4481                    _ => panic!("expected Binary"),
4482                }
4483            }
4484            _ => panic!("expected Binary"),
4485        }
4486    }
4487
4488    #[test]
4489    fn test_chumsky_parse_let_binding() {
4490        let result = parse("fn main() -> i32 { let x = 42; x }").unwrap();
4491        match &result.ast.items[0] {
4492            Item::Function(f) => match &f.body {
4493                Expr::Block(block) => {
4494                    assert_eq!(block.statements.len(), 1);
4495                    match &block.statements[0] {
4496                        Statement::Let(let_stmt) => {
4497                            assert!(!let_stmt.is_mut);
4498                            match &let_stmt.pattern {
4499                                Pattern::Ident { name, .. } => {
4500                                    assert_eq!(result.get(name.name), "x")
4501                                }
4502                                other => panic!("expected Ident, got {:?}", other),
4503                            }
4504                        }
4505                        _ => panic!("expected Let"),
4506                    }
4507                }
4508                _ => panic!("expected Block"),
4509            },
4510            Item::Struct(_) => panic!("expected Function"),
4511            Item::Enum(_) => panic!("expected Function"),
4512            Item::Interface(_) => panic!("expected Function"),
4513            Item::Derive(_) => panic!("expected Function"),
4514            Item::Const(_) => panic!("expected Function"),
4515            Item::LinkExtern(_) => panic!("expected Function"),
4516            Item::Error(_) => panic!("expected Function"),
4517        }
4518    }
4519
4520    #[test]
4521    fn test_while_simple() {
4522        // Simplest while case
4523        let result = parse("fn main() -> i32 { while true { } 0 }").unwrap();
4524        assert_eq!(result.ast.items.len(), 1);
4525    }
4526
4527    #[test]
4528    fn test_while_with_statement() {
4529        // While with assignment
4530        let result = parse("fn main() -> i32 { while true { x = 1; } 0 }").unwrap();
4531        assert_eq!(result.ast.items.len(), 1);
4532    }
4533
4534    #[test]
4535    fn test_function_calls() {
4536        let result =
4537            parse("fn add(a: i32, b: i32) -> i32 { a + b } fn main() -> i32 { add(1, 2) }")
4538                .unwrap();
4539        assert_eq!(result.ast.items.len(), 2);
4540    }
4541
4542    #[test]
4543    fn test_if_else() {
4544        let result = parse("fn main() -> i32 { if true { 1 } else { 0 } }").unwrap();
4545        assert_eq!(result.ast.items.len(), 1);
4546    }
4547
4548    #[test]
4549    fn test_nested_control_flow() {
4550        let result =
4551            parse("fn main() -> i32 { let mut x = 0; while x < 10 { x = x + 1; } x }").unwrap();
4552        assert_eq!(result.ast.items.len(), 1);
4553    }
4554
4555    // ==================== Struct Method Parsing Tests ====================
4556
4557    #[test]
4558    fn test_struct_with_single_method() {
4559        let result = parse("struct Point { x: i32, fn get_x(self) -> i32 { self.x } }").unwrap();
4560        assert_eq!(result.ast.items.len(), 1);
4561        match &result.ast.items[0] {
4562            Item::Struct(struct_decl) => {
4563                assert_eq!(result.get(struct_decl.name.name), "Point");
4564                assert_eq!(struct_decl.methods.len(), 1);
4565                let method = &struct_decl.methods[0];
4566                assert_eq!(result.get(method.name.name), "get_x");
4567                assert!(method.receiver.is_some()); // has self
4568                assert!(method.params.is_empty()); // no additional params
4569            }
4570            _ => panic!("expected Struct"),
4571        }
4572    }
4573
4574    #[test]
4575    fn test_struct_method_with_params() {
4576        let result =
4577            parse("struct Point { x: i32, fn add(self, n: i32) -> i32 { self.x + n } }").unwrap();
4578        assert_eq!(result.ast.items.len(), 1);
4579        match &result.ast.items[0] {
4580            Item::Struct(struct_decl) => {
4581                let method = &struct_decl.methods[0];
4582                assert_eq!(result.get(method.name.name), "add");
4583                assert!(method.receiver.is_some());
4584                assert_eq!(method.params.len(), 1);
4585                assert_eq!(result.get(method.params[0].name.name), "n");
4586            }
4587            _ => panic!("expected Struct"),
4588        }
4589    }
4590
4591    #[test]
4592    fn test_struct_associated_function() {
4593        // Associated function (no self)
4594        let result = parse(
4595            "struct Point { x: i32, y: i32, fn new(x: i32, y: i32) -> Point { Point { x: x, y: y } } }",
4596        )
4597        .unwrap();
4598        assert_eq!(result.ast.items.len(), 1);
4599        match &result.ast.items[0] {
4600            Item::Struct(struct_decl) => {
4601                let method = &struct_decl.methods[0];
4602                assert_eq!(result.get(method.name.name), "new");
4603                assert!(method.receiver.is_none()); // no self
4604                assert_eq!(method.params.len(), 2);
4605            }
4606            _ => panic!("expected Struct"),
4607        }
4608    }
4609
4610    #[test]
4611    fn test_struct_multiple_methods() {
4612        let result = parse(
4613            "struct Counter {
4614                 value: i32,
4615                 fn new() -> Counter { Counter { value: 0 } }
4616                 fn get(self) -> i32 { self.value }
4617                 fn increment(self) -> i32 { self.value + 1 }
4618             }",
4619        )
4620        .unwrap();
4621        assert_eq!(result.ast.items.len(), 1);
4622        match &result.ast.items[0] {
4623            Item::Struct(struct_decl) => {
4624                assert_eq!(struct_decl.methods.len(), 3);
4625                // First is associated function (no self)
4626                assert!(struct_decl.methods[0].receiver.is_none());
4627                assert_eq!(result.get(struct_decl.methods[0].name.name), "new");
4628                // Second is method (has self)
4629                assert!(struct_decl.methods[1].receiver.is_some());
4630                assert_eq!(result.get(struct_decl.methods[1].name.name), "get");
4631                // Third is method (has self)
4632                assert!(struct_decl.methods[2].receiver.is_some());
4633                assert_eq!(result.get(struct_decl.methods[2].name.name), "increment");
4634            }
4635            _ => panic!("expected Struct"),
4636        }
4637    }
4638
4639    #[test]
4640    fn test_struct_method_with_directive() {
4641        let result = parse("struct Foo { @inline fn bar(self) -> i32 { 42 } }").unwrap();
4642        match &result.ast.items[0] {
4643            Item::Struct(struct_decl) => {
4644                let method = &struct_decl.methods[0];
4645                assert_eq!(method.directives.len(), 1);
4646                assert_eq!(result.get(method.directives[0].name.name), "inline");
4647            }
4648            _ => panic!("expected Struct"),
4649        }
4650    }
4651
4652    // ==================== Field/Method Visibility (ADR-0073) ====================
4653
4654    #[test]
4655    fn test_struct_field_visibility_default_private() {
4656        let result = parse("struct Point { x: i32, y: i32 }").unwrap();
4657        match &result.ast.items[0] {
4658            Item::Struct(s) => {
4659                assert_eq!(s.fields[0].visibility, Visibility::Private);
4660                assert_eq!(s.fields[1].visibility, Visibility::Private);
4661            }
4662            _ => panic!("expected Struct"),
4663        }
4664    }
4665
4666    #[test]
4667    fn test_struct_field_visibility_pub() {
4668        let result = parse("struct Account { pub id: u64, balance: i64 }").unwrap();
4669        match &result.ast.items[0] {
4670            Item::Struct(s) => {
4671                assert_eq!(s.fields[0].visibility, Visibility::Public);
4672                assert_eq!(result.get(s.fields[0].name.name), "id");
4673                assert_eq!(s.fields[1].visibility, Visibility::Private);
4674                assert_eq!(result.get(s.fields[1].name.name), "balance");
4675            }
4676            _ => panic!("expected Struct"),
4677        }
4678    }
4679
4680    #[test]
4681    fn test_method_visibility_default_private() {
4682        let result = parse("struct Foo { fn bar(self) -> i32 { 42 } }").unwrap();
4683        match &result.ast.items[0] {
4684            Item::Struct(s) => {
4685                assert_eq!(s.methods[0].visibility, Visibility::Private);
4686            }
4687            _ => panic!("expected Struct"),
4688        }
4689    }
4690
4691    #[test]
4692    fn test_method_visibility_pub() {
4693        let result =
4694            parse("struct Foo { pub fn bar(self) -> i32 { 42 } fn helper(self) -> i32 { 1 } }")
4695                .unwrap();
4696        match &result.ast.items[0] {
4697            Item::Struct(s) => {
4698                assert_eq!(s.methods[0].visibility, Visibility::Public);
4699                assert_eq!(result.get(s.methods[0].name.name), "bar");
4700                assert_eq!(s.methods[1].visibility, Visibility::Private);
4701                assert_eq!(result.get(s.methods[1].name.name), "helper");
4702            }
4703            _ => panic!("expected Struct"),
4704        }
4705    }
4706
4707    #[test]
4708    fn test_method_visibility_pub_with_directive() {
4709        // `pub` sits between the directive and `fn`.
4710        let result = parse("struct Foo { @inline pub fn bar(self) -> i32 { 42 } }").unwrap();
4711        match &result.ast.items[0] {
4712            Item::Struct(s) => {
4713                let m = &s.methods[0];
4714                assert_eq!(m.visibility, Visibility::Public);
4715                assert_eq!(m.directives.len(), 1);
4716                assert_eq!(result.get(m.directives[0].name.name), "inline");
4717            }
4718            _ => panic!("expected Struct"),
4719        }
4720    }
4721
4722    #[test]
4723    fn test_enum_struct_variant_field_visibility() {
4724        let result = parse("enum Shape { Circle { pub r: i32, color: i32 } }").unwrap();
4725        match &result.ast.items[0] {
4726            Item::Enum(e) => {
4727                use crate::ast::EnumVariantKind;
4728                match &e.variants[0].kind {
4729                    EnumVariantKind::Struct(fields) => {
4730                        assert_eq!(fields[0].visibility, Visibility::Public);
4731                        assert_eq!(fields[1].visibility, Visibility::Private);
4732                    }
4733                    _ => panic!("expected struct variant"),
4734                }
4735            }
4736            _ => panic!("expected Enum"),
4737        }
4738    }
4739
4740    // ==================== Enum Method Parsing Tests (ADR-0053) ====================
4741
4742    #[test]
4743    fn test_enum_without_methods_still_parses() {
4744        let result = parse("enum Color { Red, Green, Blue }").unwrap();
4745        match &result.ast.items[0] {
4746            Item::Enum(enum_decl) => {
4747                assert_eq!(enum_decl.variants.len(), 3);
4748                assert!(enum_decl.methods.is_empty());
4749            }
4750            _ => panic!("expected Enum"),
4751        }
4752    }
4753
4754    #[test]
4755    fn test_enum_with_single_method() {
4756        let result =
4757            parse("enum Opt { Some(i32), None, fn is_some(self) -> bool { true } }").unwrap();
4758        match &result.ast.items[0] {
4759            Item::Enum(enum_decl) => {
4760                assert_eq!(enum_decl.variants.len(), 2);
4761                assert_eq!(enum_decl.methods.len(), 1);
4762                let m = &enum_decl.methods[0];
4763                assert_eq!(result.get(m.name.name), "is_some");
4764                assert!(m.receiver.is_some());
4765            }
4766            _ => panic!("expected Enum"),
4767        }
4768    }
4769
4770    #[test]
4771    fn test_enum_method_without_trailing_comma_on_variants() {
4772        // Methods are allowed after variants with or without a trailing comma.
4773        let result = parse("enum E { A, B fn count(self) -> i32 { 2 } }").unwrap();
4774        match &result.ast.items[0] {
4775            Item::Enum(enum_decl) => {
4776                assert_eq!(enum_decl.variants.len(), 2);
4777                assert_eq!(enum_decl.methods.len(), 1);
4778            }
4779            _ => panic!("expected Enum"),
4780        }
4781    }
4782
4783    #[test]
4784    fn test_enum_associated_function() {
4785        let result = parse("enum Opt { Some(i32), None, fn zero() -> i32 { 0 } }").unwrap();
4786        match &result.ast.items[0] {
4787            Item::Enum(enum_decl) => {
4788                let m = &enum_decl.methods[0];
4789                assert_eq!(result.get(m.name.name), "zero");
4790                assert!(m.receiver.is_none()); // associated function
4791            }
4792            _ => panic!("expected Enum"),
4793        }
4794    }
4795
4796    #[test]
4797    fn test_enum_multiple_methods() {
4798        let result = parse(
4799            "enum E {
4800                A,
4801                B,
4802                fn new() -> E { E::A }
4803                fn is_a(self) -> bool { true }
4804                fn is_b(self) -> bool { false }
4805             }",
4806        )
4807        .unwrap();
4808        match &result.ast.items[0] {
4809            Item::Enum(enum_decl) => {
4810                assert_eq!(enum_decl.methods.len(), 3);
4811                assert!(enum_decl.methods[0].receiver.is_none());
4812                assert!(enum_decl.methods[1].receiver.is_some());
4813                assert!(enum_decl.methods[2].receiver.is_some());
4814            }
4815            _ => panic!("expected Enum"),
4816        }
4817    }
4818
4819    // ==================== Method Call Parsing Tests ====================
4820
4821    #[test]
4822    fn test_method_call_simple() {
4823        let result = parse_expr("x.foo()").unwrap();
4824        match &result.expr {
4825            Expr::MethodCall(call) => {
4826                assert_eq!(result.get(call.method.name), "foo");
4827                assert!(call.args.is_empty());
4828                match call.receiver.as_ref() {
4829                    Expr::Ident(ident) => assert_eq!(result.get(ident.name), "x"),
4830                    _ => panic!("expected Ident receiver"),
4831                }
4832            }
4833            _ => panic!("expected MethodCall, got {:?}", result.expr),
4834        }
4835    }
4836
4837    #[test]
4838    fn test_method_call_with_args() {
4839        let result = parse_expr("point.add(5, 10)").unwrap();
4840        match &result.expr {
4841            Expr::MethodCall(call) => {
4842                assert_eq!(result.get(call.method.name), "add");
4843                assert_eq!(call.args.len(), 2);
4844            }
4845            _ => panic!("expected MethodCall"),
4846        }
4847    }
4848
4849    #[test]
4850    fn test_method_call_chained() {
4851        let result = parse_expr("x.foo().bar()").unwrap();
4852        match &result.expr {
4853            Expr::MethodCall(outer) => {
4854                assert_eq!(result.get(outer.method.name), "bar");
4855                match outer.receiver.as_ref() {
4856                    Expr::MethodCall(inner) => {
4857                        assert_eq!(result.get(inner.method.name), "foo");
4858                    }
4859                    _ => panic!("expected inner MethodCall"),
4860                }
4861            }
4862            _ => panic!("expected outer MethodCall"),
4863        }
4864    }
4865
4866    #[test]
4867    fn test_method_call_on_field_access() {
4868        let result = parse_expr("obj.field.method()").unwrap();
4869        match &result.expr {
4870            Expr::MethodCall(call) => {
4871                assert_eq!(result.get(call.method.name), "method");
4872                match call.receiver.as_ref() {
4873                    Expr::Field(field) => {
4874                        assert_eq!(result.get(field.field.name), "field");
4875                    }
4876                    _ => panic!("expected Field receiver"),
4877                }
4878            }
4879            _ => panic!("expected MethodCall"),
4880        }
4881    }
4882
4883    #[test]
4884    fn test_field_access_not_method_call() {
4885        // .field (not followed by parens) should parse as FieldExpr, not MethodCall
4886        let result = parse_expr("x.field").unwrap();
4887        match &result.expr {
4888            Expr::Field(field) => {
4889                assert_eq!(result.get(field.field.name), "field");
4890            }
4891            _ => panic!("expected Field, got {:?}", result.expr),
4892        }
4893    }
4894
4895    #[test]
4896    fn test_method_call_on_struct_literal() {
4897        let result =
4898            parse("struct Point { x: i32 } fn main() -> i32 { Point { x: 1 }.get() }").unwrap();
4899        match &result.ast.items[1] {
4900            Item::Function(f) => match &f.body {
4901                Expr::Block(block) => match block.expr.as_ref() {
4902                    Expr::MethodCall(call) => {
4903                        assert_eq!(result.get(call.method.name), "get");
4904                        match call.receiver.as_ref() {
4905                            Expr::StructLit(lit) => {
4906                                assert_eq!(result.get(lit.name.name), "Point")
4907                            }
4908                            _ => panic!("expected StructLit receiver"),
4909                        }
4910                    }
4911                    _ => panic!("expected MethodCall"),
4912                },
4913                _ => panic!("expected Block"),
4914            },
4915            _ => panic!("expected Function"),
4916        }
4917    }
4918
4919    #[test]
4920    fn test_method_call_on_paren_expr() {
4921        let result = parse_expr("(x).method()").unwrap();
4922        match &result.expr {
4923            Expr::MethodCall(call) => {
4924                assert_eq!(result.get(call.method.name), "method");
4925                match call.receiver.as_ref() {
4926                    Expr::Paren(_) => {}
4927                    _ => panic!("expected Paren receiver"),
4928                }
4929            }
4930            _ => panic!("expected MethodCall"),
4931        }
4932    }
4933
4934    #[test]
4935    fn test_method_call_mixed_with_index() {
4936        // Complex chain: array[0].method().field
4937        let result = parse_expr("arr[0].get().value").unwrap();
4938        match &result.expr {
4939            Expr::Field(field) => {
4940                assert_eq!(result.get(field.field.name), "value");
4941                match field.base.as_ref() {
4942                    Expr::MethodCall(call) => {
4943                        assert_eq!(result.get(call.method.name), "get");
4944                        match call.receiver.as_ref() {
4945                            Expr::Index(_) => {}
4946                            _ => panic!("expected Index receiver"),
4947                        }
4948                    }
4949                    _ => panic!("expected MethodCall"),
4950                }
4951            }
4952            _ => panic!("expected Field"),
4953        }
4954    }
4955
4956    #[test]
4957    fn test_associated_function_call() {
4958        // Type::function(args) syntax
4959        let result = parse_expr("Point::new(1, 2)").unwrap();
4960        match &result.expr {
4961            Expr::AssocFnCall(call) => {
4962                assert_eq!(result.get(call.type_name.name), "Point");
4963                assert_eq!(result.get(call.function.name), "new");
4964                assert_eq!(call.args.len(), 2);
4965            }
4966            _ => panic!("expected AssocFnCall, got {:?}", result.expr),
4967        }
4968    }
4969
4970    // ==================== Block Expression Statement Tests ====================
4971
4972    #[test]
4973    fn test_block_statement_followed_by_identifier() {
4974        // Block expression as a statement followed by an identifier expression
4975        // This is the regression test for gruel-wo1g
4976        let result = parse(
4977            "fn main() -> i32 {
4978                let a = 1;
4979                {
4980                    let b = 2;
4981                }
4982                a
4983            }",
4984        )
4985        .unwrap();
4986        match &result.ast.items[0] {
4987            Item::Function(f) => match &f.body {
4988                Expr::Block(block) => {
4989                    // Should have 2 statements: let a = 1; and { let b = 2; }
4990                    assert_eq!(block.statements.len(), 2);
4991                    // First statement is a let
4992                    assert!(matches!(&block.statements[0], Statement::Let(_)));
4993                    // Second statement is an expression statement containing a block
4994                    match &block.statements[1] {
4995                        Statement::Expr(Expr::Block(_)) => {}
4996                        _ => panic!("expected Expr(Block), got {:?}", block.statements[1]),
4997                    }
4998                    // Final expression should be 'a' (simple identifier)
4999                    match block.expr.as_ref() {
5000                        Expr::Ident(ident) => assert_eq!(result.get(ident.name), "a"),
5001                        _ => panic!("expected Ident expression, got {:?}", block.expr),
5002                    }
5003                }
5004                _ => panic!("expected Block"),
5005            },
5006            _ => panic!("expected Function"),
5007        }
5008    }
5009
5010    // ==================== Error Conversion Tests ====================
5011
5012    #[test]
5013    fn test_error_preserves_span() {
5014        // Parse invalid syntax and verify error has span information
5015        let result = parse("fn main() -> i32 { let = 42; }");
5016        assert!(result.is_err());
5017        let errors = result.unwrap_err();
5018        // Get the first error and verify it has span information
5019        let error = errors.first().expect("should have at least one error");
5020        assert!(error.has_span());
5021        assert!(error.span().is_some());
5022    }
5023
5024    #[test]
5025    fn test_error_expected_found() {
5026        // Missing expression after let
5027        let result = parse("fn main() -> i32 { let x = ; }");
5028        assert!(result.is_err());
5029        let errors = result.unwrap_err();
5030        let error = errors.first().expect("should have at least one error");
5031        // Error message should describe what was expected vs found
5032        let msg = error.to_string();
5033        assert!(
5034            msg.contains("expected") || msg.contains("found"),
5035            "error message: {}",
5036            msg
5037        );
5038    }
5039
5040    #[test]
5041    fn test_error_unexpected_eof() {
5042        // Unterminated block
5043        let result = parse("fn main() -> i32 {");
5044        assert!(result.is_err());
5045        let errors = result.unwrap_err();
5046        let error = errors.first().expect("should have at least one error");
5047        let msg = error.to_string();
5048        // Should indicate end of file was reached unexpectedly
5049        assert!(
5050            msg.contains("end of file") || msg.contains("expected"),
5051            "error message: {}",
5052            msg
5053        );
5054    }
5055
5056    #[test]
5057    fn test_format_pattern_token() {
5058        use chumsky::error::RichPattern;
5059        use chumsky::util::MaybeRef;
5060
5061        let pattern = RichPattern::Token(MaybeRef::Val(TokenKind::Plus));
5062        let formatted = format_pattern(&pattern);
5063        // TokenKind::name() returns quoted form like "'+'"
5064        assert_eq!(formatted, "'+'");
5065    }
5066
5067    #[test]
5068    fn test_format_pattern_label() {
5069        use chumsky::error::RichPattern;
5070
5071        let pattern: RichPattern<'_, TokenKind> = RichPattern::Label(Cow::Borrowed("expression"));
5072        let formatted = format_pattern(&pattern);
5073        assert_eq!(formatted, "expression");
5074    }
5075
5076    #[test]
5077    fn test_format_pattern_identifier() {
5078        use chumsky::error::RichPattern;
5079
5080        let pattern: RichPattern<'_, TokenKind> = RichPattern::Identifier("while".to_string());
5081        let formatted = format_pattern(&pattern);
5082        assert_eq!(formatted, "'while'");
5083    }
5084
5085    #[test]
5086    fn test_format_pattern_any() {
5087        use chumsky::error::RichPattern;
5088
5089        let pattern: RichPattern<'_, TokenKind> = RichPattern::Any;
5090        let formatted = format_pattern(&pattern);
5091        assert_eq!(formatted, "any token");
5092    }
5093
5094    #[test]
5095    fn test_format_pattern_end_of_input() {
5096        use chumsky::error::RichPattern;
5097
5098        let pattern: RichPattern<'_, TokenKind> = RichPattern::EndOfInput;
5099        let formatted = format_pattern(&pattern);
5100        assert_eq!(formatted, "end of input");
5101    }
5102
5103    #[test]
5104    fn test_parse_error_no_empty_found_clause() {
5105        // Test that error messages don't have empty "found" clauses
5106        // This was a bug when Custom errors were mapped to UnexpectedToken with empty found
5107        let result = parse("fn main() -> i32 { let x = ; }");
5108        assert!(result.is_err());
5109        let errors = result.unwrap_err();
5110        let error = errors.first().expect("should have at least one error");
5111        let msg = error.to_string();
5112        // Should not end with "found " (empty found)
5113        assert!(
5114            !msg.ends_with("found "),
5115            "error message should not have trailing empty 'found': {}",
5116            msg
5117        );
5118    }
5119
5120    #[test]
5121    fn test_parse_error_variant_display() {
5122        // Directly test that ParseError displays correctly
5123        let error = CompileError::new(
5124            ErrorKind::ParseError("expected semicolon after expression".to_string()),
5125            gruel_util::Span::new(0, 10),
5126        );
5127        assert_eq!(error.to_string(), "expected semicolon after expression");
5128    }
5129
5130    #[test]
5131    fn test_offset_to_u32_normal() {
5132        // Normal values should convert without issue
5133        assert_eq!(offset_to_u32(0), 0);
5134        assert_eq!(offset_to_u32(42), 42);
5135        assert_eq!(offset_to_u32(1000000), 1000000);
5136        assert_eq!(offset_to_u32(u32::MAX as usize), u32::MAX);
5137    }
5138
5139    #[test]
5140    #[should_panic(expected = "offset 4294967296 exceeds u32::MAX")]
5141    #[cfg(debug_assertions)]
5142    fn test_offset_to_u32_overflow_panics_in_debug() {
5143        // Value just over u32::MAX should panic in debug builds
5144        let _ = offset_to_u32((u32::MAX as usize) + 1);
5145    }
5146
5147    #[test]
5148    fn test_to_gruel_util_normal() {
5149        // Normal spans should convert without issue
5150        let simple = SimpleSpan::new(10, 20);
5151        let gruel = to_gruel_util(simple);
5152        assert_eq!(gruel.start, 10);
5153        assert_eq!(gruel.end, 20);
5154    }
5155
5156    #[test]
5157    fn test_parse_returns_multiple_errors() {
5158        // Test that we return ALL parse errors, not just the first one.
5159        // This uses source with multiple syntax errors that can be detected at once.
5160        // Note: The actual number of errors depends on Chumsky's error recovery,
5161        // but this test ensures the infrastructure returns all errors it finds.
5162        let source = "fn main() { let }"; // Missing variable name and expression
5163
5164        let result = parse(source);
5165        assert!(result.is_err(), "Expected parsing to fail");
5166
5167        // We should get at least one error
5168        let errors = result.unwrap_err();
5169        assert!(
5170            !errors.is_empty(),
5171            "Expected at least one error but got none"
5172        );
5173
5174        // Verify we can iterate over all errors
5175        let error_count = errors.len();
5176        assert!(
5177            error_count >= 1,
5178            "Expected at least 1 error, got {}",
5179            error_count
5180        );
5181    }
5182
5183    #[test]
5184    fn test_parse_error_collection_preserves_all() {
5185        // Test that the error collection mechanism preserves all errors.
5186        // This directly tests the CompileErrors::from(Vec<CompileError>) path.
5187        let errors = vec![
5188            CompileError::without_span(ErrorKind::UnexpectedToken {
5189                expected: std::borrow::Cow::Borrowed("ident"),
5190                found: std::borrow::Cow::Borrowed("let"),
5191            }),
5192            CompileError::without_span(ErrorKind::UnexpectedToken {
5193                expected: std::borrow::Cow::Borrowed("expr"),
5194                found: std::borrow::Cow::Borrowed("rbrace"),
5195            }),
5196        ];
5197
5198        let compile_errors = CompileErrors::from(errors);
5199        assert_eq!(compile_errors.len(), 2, "Expected 2 errors to be preserved");
5200    }
5201
5202    // ==================== Qualified Struct Literal Tests ====================
5203
5204    #[test]
5205    fn test_qualified_struct_literal() {
5206        // module.Point { x: 1, y: 2 } should parse as a qualified struct literal
5207        let result = parse_expr("mod.Point { x: 1, y: 2 }").unwrap();
5208        match &result.expr {
5209            Expr::StructLit(lit) => {
5210                // Verify it has a base (the module)
5211                assert!(
5212                    lit.base.is_some(),
5213                    "qualified struct literal should have a base"
5214                );
5215                match lit.base.as_ref().unwrap().as_ref() {
5216                    Expr::Ident(ident) => assert_eq!(result.get(ident.name), "mod"),
5217                    _ => panic!("base should be Ident, got {:?}", lit.base),
5218                }
5219                // Verify struct name
5220                assert_eq!(result.get(lit.name.name), "Point");
5221                // Verify fields
5222                assert_eq!(lit.fields.len(), 2);
5223                assert_eq!(result.get(lit.fields[0].name.name), "x");
5224                assert_eq!(result.get(lit.fields[1].name.name), "y");
5225            }
5226            _ => panic!("expected StructLit, got {:?}", result.expr),
5227        }
5228    }
5229
5230    #[test]
5231    fn test_qualified_struct_literal_empty() {
5232        // module.Empty {} should parse as a qualified struct literal with no fields
5233        let result = parse_expr("mod.Empty {}").unwrap();
5234        match &result.expr {
5235            Expr::StructLit(lit) => {
5236                assert!(
5237                    lit.base.is_some(),
5238                    "qualified struct literal should have a base"
5239                );
5240                assert_eq!(result.get(lit.name.name), "Empty");
5241                assert_eq!(lit.fields.len(), 0);
5242            }
5243            _ => panic!("expected StructLit, got {:?}", result.expr),
5244        }
5245    }
5246
5247    #[test]
5248    fn test_field_access_then_block() {
5249        // obj.field; { 1 } should parse as field access (discarded) followed by block expression
5250        // Note: obj.field { 1 } (without semicolon) is a syntax error because there's no
5251        // operator between the field access and the block.
5252        let result = parse("fn main() -> i32 { x.field; { 1 } }").unwrap();
5253        match &result.ast.items[0] {
5254            Item::Function(f) => match &f.body {
5255                Expr::Block(block) => {
5256                    // The block should have a statement (field access discarded) and
5257                    // a final expression (the inner block returning 1)
5258                    assert_eq!(block.statements.len(), 1);
5259                    match &block.statements[0] {
5260                        Statement::Expr(Expr::Field(field)) => {
5261                            assert_eq!(result.get(field.field.name), "field");
5262                        }
5263                        _ => panic!("expected Field statement, got {:?}", block.statements[0]),
5264                    }
5265                    match block.expr.as_ref() {
5266                        Expr::Block(inner) => match inner.expr.as_ref() {
5267                            Expr::Int(lit) => assert_eq!(lit.value, 1),
5268                            _ => panic!("expected Int, got {:?}", inner.expr),
5269                        },
5270                        _ => panic!("expected Block, got {:?}", block.expr),
5271                    }
5272                }
5273                _ => panic!("expected Block"),
5274            },
5275            _ => panic!("expected Function"),
5276        }
5277    }
5278
5279    #[test]
5280    fn test_field_access_block_without_semicolon_is_error() {
5281        // obj.field { 1 } without semicolon is a syntax error - it's not a struct literal
5282        // (because { 1 } doesn't match the { ident: } pattern) and it's not valid syntax.
5283        let result = parse("fn main() -> i32 { x.field { 1 } }");
5284        assert!(
5285            result.is_err(),
5286            "field + block without semicolon should be syntax error"
5287        );
5288    }
5289
5290    #[test]
5291    fn test_chained_qualified_struct_literal() {
5292        // a.b.Point { x: 1 } - nested field access then struct literal
5293        let result = parse_expr("a.b.Point { x: 1 }").unwrap();
5294        match &result.expr {
5295            Expr::StructLit(lit) => {
5296                // Should have a base that's a.b
5297                assert!(lit.base.is_some());
5298                match lit.base.as_ref().unwrap().as_ref() {
5299                    Expr::Field(field) => {
5300                        assert_eq!(result.get(field.field.name), "b");
5301                        match field.base.as_ref() {
5302                            Expr::Ident(ident) => assert_eq!(result.get(ident.name), "a"),
5303                            _ => panic!("inner base should be Ident"),
5304                        }
5305                    }
5306                    _ => panic!("base should be Field, got {:?}", lit.base),
5307                }
5308                assert_eq!(result.get(lit.name.name), "Point");
5309            }
5310            _ => panic!("expected StructLit, got {:?}", result.expr),
5311        }
5312    }
5313
5314    // ==================== Anonymous Struct Method Parsing Tests ====================
5315
5316    #[test]
5317    fn test_anon_struct_with_fields_only() {
5318        // Anonymous struct with only fields (no methods)
5319        let result = parse("fn make_type() -> type { struct { x: i32, y: i32 } }").unwrap();
5320        match &result.ast.items[0] {
5321            Item::Function(f) => {
5322                assert_eq!(result.get(f.name.name), "make_type");
5323                match &f.body {
5324                    Expr::Block(block) => match block.expr.as_ref() {
5325                        Expr::TypeLit(type_lit) => match &type_lit.type_expr {
5326                            TypeExpr::AnonymousStruct {
5327                                fields, methods, ..
5328                            } => {
5329                                assert_eq!(fields.len(), 2);
5330                                assert_eq!(result.get(fields[0].name.name), "x");
5331                                assert_eq!(result.get(fields[1].name.name), "y");
5332                                assert!(methods.is_empty());
5333                            }
5334                            _ => panic!("expected AnonymousStruct"),
5335                        },
5336                        _ => panic!("expected TypeLit"),
5337                    },
5338                    _ => panic!("expected Block"),
5339                }
5340            }
5341            _ => panic!("expected Function"),
5342        }
5343    }
5344
5345    #[test]
5346    fn test_anon_struct_with_method() {
5347        // Anonymous struct with a single method
5348        let result =
5349            parse("fn make_type() -> type { struct { x: i32, fn get_x(self) -> i32 { self.x } } }")
5350                .unwrap();
5351        match &result.ast.items[0] {
5352            Item::Function(f) => match &f.body {
5353                Expr::Block(block) => match block.expr.as_ref() {
5354                    Expr::TypeLit(type_lit) => match &type_lit.type_expr {
5355                        TypeExpr::AnonymousStruct {
5356                            fields, methods, ..
5357                        } => {
5358                            assert_eq!(fields.len(), 1);
5359                            assert_eq!(result.get(fields[0].name.name), "x");
5360                            assert_eq!(methods.len(), 1);
5361                            assert_eq!(result.get(methods[0].name.name), "get_x");
5362                            assert!(
5363                                methods[0].receiver.is_some(),
5364                                "method should have self receiver"
5365                            );
5366                        }
5367                        _ => panic!("expected AnonymousStruct"),
5368                    },
5369                    _ => panic!("expected TypeLit"),
5370                },
5371                _ => panic!("expected Block"),
5372            },
5373            _ => panic!("expected Function"),
5374        }
5375    }
5376
5377    #[test]
5378    fn test_anon_struct_with_associated_function() {
5379        // Anonymous struct with an associated function (no self)
5380        let result = parse(
5381            "fn make_type() -> type { struct { x: i32, fn new() -> Self { Self { x: 0 } } } }",
5382        )
5383        .unwrap();
5384        match &result.ast.items[0] {
5385            Item::Function(f) => match &f.body {
5386                Expr::Block(block) => match block.expr.as_ref() {
5387                    Expr::TypeLit(type_lit) => match &type_lit.type_expr {
5388                        TypeExpr::AnonymousStruct {
5389                            fields, methods, ..
5390                        } => {
5391                            assert_eq!(fields.len(), 1);
5392                            assert_eq!(methods.len(), 1);
5393                            assert_eq!(result.get(methods[0].name.name), "new");
5394                            assert!(
5395                                methods[0].receiver.is_none(),
5396                                "associated function should not have self"
5397                            );
5398                            // Check return type is Self
5399                            match &methods[0].return_type {
5400                                Some(TypeExpr::Named(ident)) => {
5401                                    assert_eq!(result.get(ident.name), "Self");
5402                                }
5403                                _ => panic!("expected Self return type"),
5404                            }
5405                        }
5406                        _ => panic!("expected AnonymousStruct"),
5407                    },
5408                    _ => panic!("expected TypeLit"),
5409                },
5410                _ => panic!("expected Block"),
5411            },
5412            _ => panic!("expected Function"),
5413        }
5414    }
5415
5416    #[test]
5417    fn test_anon_struct_with_multiple_methods() {
5418        // Anonymous struct with multiple methods
5419        let result = parse(
5420            r#"
5421            fn make_type() -> type {
5422                struct {
5423                    value: i32,
5424                    fn get(self) -> i32 { self.value }
5425                    fn set(self, v: i32) -> Self { Self { value: v } }
5426                }
5427            }
5428        "#,
5429        )
5430        .unwrap();
5431        match &result.ast.items[0] {
5432            Item::Function(f) => match &f.body {
5433                Expr::Block(block) => match block.expr.as_ref() {
5434                    Expr::TypeLit(type_lit) => match &type_lit.type_expr {
5435                        TypeExpr::AnonymousStruct {
5436                            fields, methods, ..
5437                        } => {
5438                            assert_eq!(fields.len(), 1);
5439                            assert_eq!(result.get(fields[0].name.name), "value");
5440                            assert_eq!(methods.len(), 2);
5441                            assert_eq!(result.get(methods[0].name.name), "get");
5442                            assert_eq!(result.get(methods[1].name.name), "set");
5443                            // Check set has a parameter
5444                            assert_eq!(methods[1].params.len(), 1);
5445                            assert_eq!(result.get(methods[1].params[0].name.name), "v");
5446                        }
5447                        _ => panic!("expected AnonymousStruct"),
5448                    },
5449                    _ => panic!("expected TypeLit"),
5450                },
5451                _ => panic!("expected Block"),
5452            },
5453            _ => panic!("expected Function"),
5454        }
5455    }
5456
5457    #[test]
5458    fn test_anon_struct_methods_only() {
5459        // Anonymous struct with only methods (no fields)
5460        let result =
5461            parse("fn make_type() -> type { struct { fn new() -> Self { Self { } } } }").unwrap();
5462        match &result.ast.items[0] {
5463            Item::Function(f) => match &f.body {
5464                Expr::Block(block) => match block.expr.as_ref() {
5465                    Expr::TypeLit(type_lit) => match &type_lit.type_expr {
5466                        TypeExpr::AnonymousStruct {
5467                            fields, methods, ..
5468                        } => {
5469                            assert!(fields.is_empty());
5470                            assert_eq!(methods.len(), 1);
5471                            assert_eq!(result.get(methods[0].name.name), "new");
5472                        }
5473                        _ => panic!("expected AnonymousStruct"),
5474                    },
5475                    _ => panic!("expected TypeLit"),
5476                },
5477                _ => panic!("expected Block"),
5478            },
5479            _ => panic!("expected Function"),
5480        }
5481    }
5482
5483    #[test]
5484    fn test_self_type_in_return() {
5485        // Test Self as return type
5486        let result =
5487            parse("fn make_type() -> type { struct { x: i32, fn clone(self) -> Self { self } } }")
5488                .unwrap();
5489        match &result.ast.items[0] {
5490            Item::Function(f) => match &f.body {
5491                Expr::Block(block) => match block.expr.as_ref() {
5492                    Expr::TypeLit(type_lit) => match &type_lit.type_expr {
5493                        TypeExpr::AnonymousStruct { methods, .. } => {
5494                            match &methods[0].return_type {
5495                                Some(TypeExpr::Named(ident)) => {
5496                                    assert_eq!(result.get(ident.name), "Self");
5497                                }
5498                                _ => panic!("expected Self return type"),
5499                            }
5500                        }
5501                        _ => panic!("expected AnonymousStruct"),
5502                    },
5503                    _ => panic!("expected TypeLit"),
5504                },
5505                _ => panic!("expected Block"),
5506            },
5507            _ => panic!("expected Function"),
5508        }
5509    }
5510
5511    #[test]
5512    fn test_self_type_in_param() {
5513        // Test Self as parameter type
5514        let result = parse(
5515            "fn make_type() -> type { struct { fn combine(self, other: Self) -> Self { self } } }",
5516        )
5517        .unwrap();
5518        match &result.ast.items[0] {
5519            Item::Function(f) => match &f.body {
5520                Expr::Block(block) => match block.expr.as_ref() {
5521                    Expr::TypeLit(type_lit) => match &type_lit.type_expr {
5522                        TypeExpr::AnonymousStruct { methods, .. } => {
5523                            // Check parameter type is Self
5524                            let param = &methods[0].params[0];
5525                            match &param.ty {
5526                                TypeExpr::Named(ident) => {
5527                                    assert_eq!(result.get(ident.name), "Self");
5528                                }
5529                                _ => panic!("expected Self param type"),
5530                            }
5531                        }
5532                        _ => panic!("expected AnonymousStruct"),
5533                    },
5534                    _ => panic!("expected TypeLit"),
5535                },
5536                _ => panic!("expected Block"),
5537            },
5538            _ => panic!("expected Function"),
5539        }
5540    }
5541
5542    #[test]
5543    fn test_self_type_standalone() {
5544        // Test Self as a standalone type (e.g., in struct method context)
5545        // This just tests lexing/parsing of Self as a type keyword
5546        let result = parse("struct Foo { x: i32, fn clone(self) -> Self { self } }").unwrap();
5547        match &result.ast.items[0] {
5548            Item::Struct(struct_decl) => {
5549                let method = &struct_decl.methods[0];
5550                match &method.return_type {
5551                    Some(TypeExpr::Named(ident)) => {
5552                        assert_eq!(result.get(ident.name), "Self");
5553                    }
5554                    _ => panic!("expected Self return type"),
5555                }
5556            }
5557            _ => panic!("expected Struct"),
5558        }
5559    }
5560
5561    // ========================================================================
5562    // Tuple parser tests (ADR-0048, phase 1)
5563    // ========================================================================
5564
5565    #[test]
5566    fn test_tuple_expr_pair() {
5567        let result = parse_expr("(1, 2)").unwrap();
5568        match &result.expr {
5569            Expr::Tuple(t) => {
5570                assert_eq!(t.elems.len(), 2);
5571                match &t.elems[0] {
5572                    Expr::Int(lit) => assert_eq!(lit.value, 1),
5573                    _ => panic!("expected Int"),
5574                }
5575                match &t.elems[1] {
5576                    Expr::Int(lit) => assert_eq!(lit.value, 2),
5577                    _ => panic!("expected Int"),
5578                }
5579            }
5580            other => panic!("expected Tuple, got {:?}", other),
5581        }
5582    }
5583
5584    #[test]
5585    fn test_tuple_expr_triple_mixed() {
5586        let result = parse_expr("(1, true, 3)").unwrap();
5587        match &result.expr {
5588            Expr::Tuple(t) => {
5589                assert_eq!(t.elems.len(), 3);
5590                assert!(matches!(t.elems[0], Expr::Int(_)));
5591                assert!(matches!(t.elems[1], Expr::Bool(_)));
5592                assert!(matches!(t.elems[2], Expr::Int(_)));
5593            }
5594            _ => panic!("expected Tuple"),
5595        }
5596    }
5597
5598    #[test]
5599    fn test_tuple_expr_singleton_needs_trailing_comma() {
5600        // (x,) is a 1-tuple
5601        let result = parse_expr("(42,)").unwrap();
5602        match &result.expr {
5603            Expr::Tuple(t) => {
5604                assert_eq!(t.elems.len(), 1);
5605                assert!(matches!(t.elems[0], Expr::Int(_)));
5606            }
5607            _ => panic!("expected Tuple singleton"),
5608        }
5609    }
5610
5611    #[test]
5612    fn test_paren_expr_without_comma_is_not_tuple() {
5613        // (x) stays a parenthesised expression, not a 1-tuple
5614        let result = parse_expr("(42)").unwrap();
5615        assert!(matches!(result.expr, Expr::Paren(_)));
5616    }
5617
5618    #[test]
5619    fn test_unit_literal_is_not_tuple() {
5620        let result = parse_expr("()").unwrap();
5621        assert!(matches!(result.expr, Expr::Unit(_)));
5622    }
5623
5624    #[test]
5625    fn test_tuple_expr_trailing_comma_allowed() {
5626        let result = parse_expr("(1, 2,)").unwrap();
5627        match &result.expr {
5628            Expr::Tuple(t) => assert_eq!(t.elems.len(), 2),
5629            _ => panic!("expected Tuple"),
5630        }
5631    }
5632
5633    #[test]
5634    fn test_tuple_index_zero() {
5635        let result = parse_expr("t.0").unwrap();
5636        match &result.expr {
5637            Expr::TupleIndex(ti) => {
5638                assert_eq!(ti.index, 0);
5639                assert!(matches!(*ti.base, Expr::Ident(_)));
5640            }
5641            _ => panic!("expected TupleIndex"),
5642        }
5643    }
5644
5645    #[test]
5646    fn test_tuple_index_multi_digit() {
5647        let result = parse_expr("t.42").unwrap();
5648        match &result.expr {
5649            Expr::TupleIndex(ti) => assert_eq!(ti.index, 42),
5650            _ => panic!("expected TupleIndex"),
5651        }
5652    }
5653
5654    #[test]
5655    fn test_parenthesised_nested_tuple_access() {
5656        // (t.0).1 works; t.0.1 doesn't because the lexer treats `0.1` as Float
5657        let result = parse_expr("(t.0).1").unwrap();
5658        match &result.expr {
5659            Expr::TupleIndex(outer) => {
5660                assert_eq!(outer.index, 1);
5661                match &*outer.base {
5662                    Expr::Paren(p) => match &*p.inner {
5663                        Expr::TupleIndex(inner) => assert_eq!(inner.index, 0),
5664                        _ => panic!("expected inner TupleIndex"),
5665                    },
5666                    _ => panic!("expected Paren wrapping inner access"),
5667                }
5668            }
5669            _ => panic!("expected outer TupleIndex"),
5670        }
5671    }
5672
5673    #[test]
5674    fn test_tuple_type_pair() {
5675        let result = parse("fn f() -> (i32, bool) { (1, true) }").unwrap();
5676        match &result.ast.items[0] {
5677            Item::Function(f) => match f.return_type.as_ref().unwrap() {
5678                TypeExpr::Tuple { elems, .. } => {
5679                    assert_eq!(elems.len(), 2);
5680                }
5681                other => panic!("expected Tuple type, got {:?}", other),
5682            },
5683            _ => panic!("expected Function"),
5684        }
5685    }
5686
5687    #[test]
5688    fn test_tuple_type_singleton() {
5689        let result = parse("fn f() -> (i32,) { (1,) }").unwrap();
5690        match &result.ast.items[0] {
5691            Item::Function(f) => match f.return_type.as_ref().unwrap() {
5692                TypeExpr::Tuple { elems, .. } => assert_eq!(elems.len(), 1),
5693                _ => panic!("expected Tuple type"),
5694            },
5695            _ => panic!("expected Function"),
5696        }
5697    }
5698
5699    #[test]
5700    fn test_unit_type_still_unit() {
5701        let result = parse("fn f() -> () { () }").unwrap();
5702        match &result.ast.items[0] {
5703            Item::Function(f) => match f.return_type.as_ref().unwrap() {
5704                TypeExpr::Unit(_) => {}
5705                other => panic!("expected Unit type, got {:?}", other),
5706            },
5707            _ => panic!("expected Function"),
5708        }
5709    }
5710
5711    fn assert_tuple_ident(
5712        elem: &TupleElemPattern,
5713        result: &ParseResult,
5714        expected: &str,
5715        expected_mut: bool,
5716    ) {
5717        match elem {
5718            TupleElemPattern::Pattern(Pattern::Ident { name, is_mut, .. }) => {
5719                assert_eq!(result.get(name.name), expected);
5720                assert_eq!(*is_mut, expected_mut);
5721            }
5722            other => panic!("expected Pattern::Ident, got {:?}", other),
5723        }
5724    }
5725
5726    #[test]
5727    fn test_tuple_destructure_basic() {
5728        let result = parse("fn main() -> i32 { let (a, b) = (1, 2); a }").unwrap();
5729        match &result.ast.items[0] {
5730            Item::Function(f) => match &f.body {
5731                Expr::Block(block) => match &block.statements[0] {
5732                    Statement::Let(let_stmt) => match &let_stmt.pattern {
5733                        Pattern::Tuple { elems, .. } => {
5734                            assert_eq!(elems.len(), 2);
5735                            assert_tuple_ident(&elems[0], &result, "a", false);
5736                            assert_tuple_ident(&elems[1], &result, "b", false);
5737                        }
5738                        _ => panic!("expected Tuple pattern"),
5739                    },
5740                    _ => panic!("expected Let"),
5741                },
5742                _ => panic!("expected Block"),
5743            },
5744            _ => panic!("expected Function"),
5745        }
5746    }
5747
5748    #[test]
5749    fn test_tuple_destructure_mut_and_wildcard() {
5750        let result = parse("fn main() -> i32 { let (mut a, _) = (1, 2); a }").unwrap();
5751        match &result.ast.items[0] {
5752            Item::Function(f) => match &f.body {
5753                Expr::Block(block) => match &block.statements[0] {
5754                    Statement::Let(let_stmt) => match &let_stmt.pattern {
5755                        Pattern::Tuple { elems, .. } => {
5756                            assert_eq!(elems.len(), 2);
5757                            assert_tuple_ident(&elems[0], &result, "a", true);
5758                            assert!(matches!(
5759                                &elems[1],
5760                                TupleElemPattern::Pattern(Pattern::Wildcard(_))
5761                            ));
5762                        }
5763                        _ => panic!("expected Tuple pattern"),
5764                    },
5765                    _ => panic!("expected Let"),
5766                },
5767                _ => panic!("expected Block"),
5768            },
5769            _ => panic!("expected Function"),
5770        }
5771    }
5772
5773    #[test]
5774    fn test_tuple_destructure_singleton() {
5775        let result = parse("fn main() -> i32 { let (x,) = (42,); x }").unwrap();
5776        match &result.ast.items[0] {
5777            Item::Function(f) => match &f.body {
5778                Expr::Block(block) => match &block.statements[0] {
5779                    Statement::Let(let_stmt) => match &let_stmt.pattern {
5780                        Pattern::Tuple { elems, .. } => {
5781                            assert_eq!(elems.len(), 1);
5782                        }
5783                        _ => panic!("expected Tuple pattern"),
5784                    },
5785                    _ => panic!("expected Let"),
5786                },
5787                _ => panic!("expected Block"),
5788            },
5789            _ => panic!("expected Function"),
5790        }
5791    }
5792
5793    // ==================== ADR-0049: nested pattern shapes ====================
5794    //
5795    // These tests drive the parser on nested pattern shapes. Previously the
5796    // post-parse validator gated them behind the `nested_patterns` preview
5797    // feature; now the feature is stabilized so parsing just works.
5798
5799    fn parse_with_nested(source: &str) -> MultiErrorResult<ParseResult> {
5800        let lexer = Lexer::new(source);
5801        let (tokens, interner) = lexer.tokenize().unwrap();
5802        let parser = ChumskyParser::new(tokens, interner);
5803        parser
5804            .parse()
5805            .map(|(ast, interner)| ParseResult { ast, interner })
5806    }
5807
5808    fn find_first_let_pattern(result: &ParseResult) -> &Pattern {
5809        match &result.ast.items[0] {
5810            Item::Function(f) => match &f.body {
5811                Expr::Block(block) => match &block.statements[0] {
5812                    Statement::Let(l) => &l.pattern,
5813                    _ => panic!("expected let"),
5814                },
5815                _ => panic!("expected block"),
5816            },
5817            _ => panic!("expected function"),
5818        }
5819    }
5820
5821    fn find_first_match_arm_pattern(result: &ParseResult) -> &Pattern {
5822        match &result.ast.items[0] {
5823            Item::Function(f) => {
5824                fn find_match(e: &Expr) -> Option<&Pattern> {
5825                    match e {
5826                        Expr::Block(b) => find_match(&b.expr),
5827                        Expr::Match(m) => Some(&m.arms[0].pattern),
5828                        _ => None,
5829                    }
5830                }
5831                find_match(&f.body).expect("expected a match expression")
5832            }
5833            _ => panic!("expected function"),
5834        }
5835    }
5836
5837    #[test]
5838    fn test_nested_let_struct_in_struct() {
5839        let source = r#"
5840            fn main() -> i32 {
5841                let Outer { inner: Inner { x, y }, tag } = make();
5842                0
5843            }
5844        "#;
5845        let result = parse_with_nested(source).unwrap();
5846        match find_first_let_pattern(&result) {
5847            Pattern::Struct { fields, .. } => {
5848                assert_eq!(fields.len(), 2);
5849                // inner: Inner { x, y }
5850                let inner = &fields[0];
5851                assert_eq!(result.get(inner.field_name.unwrap().name), "inner");
5852                match inner.sub.as_ref().unwrap() {
5853                    Pattern::Struct {
5854                        fields: inner_fields,
5855                        ..
5856                    } => assert_eq!(inner_fields.len(), 2),
5857                    other => panic!("expected nested Struct, got {:?}", other),
5858                }
5859                // tag (shorthand)
5860                let tag = &fields[1];
5861                assert_eq!(result.get(tag.field_name.unwrap().name), "tag");
5862                assert!(tag.sub.is_none());
5863            }
5864            other => panic!("expected Pattern::Struct, got {:?}", other),
5865        }
5866    }
5867
5868    #[test]
5869    fn test_nested_let_tuple_of_tuples() {
5870        let source = "fn main() -> i32 { let ((a, b), c) = ((1, 2), 3); 0 }";
5871        let result = parse_with_nested(source).unwrap();
5872        match find_first_let_pattern(&result) {
5873            Pattern::Tuple { elems, .. } => {
5874                assert_eq!(elems.len(), 2);
5875                match &elems[0] {
5876                    TupleElemPattern::Pattern(Pattern::Tuple {
5877                        elems: inner_elems, ..
5878                    }) => assert_eq!(inner_elems.len(), 2),
5879                    other => panic!("expected nested tuple, got {:?}", other),
5880                }
5881            }
5882            other => panic!("expected Pattern::Tuple, got {:?}", other),
5883        }
5884    }
5885
5886    #[test]
5887    fn test_tuple_pattern_in_match() {
5888        let source = r#"
5889            fn main() -> i32 {
5890                match pair() {
5891                    (0, 0) => 0,
5892                    _ => 1,
5893                }
5894            }
5895        "#;
5896        let result = parse_with_nested(source).unwrap();
5897        match find_first_match_arm_pattern(&result) {
5898            Pattern::Tuple { elems, .. } => {
5899                assert_eq!(elems.len(), 2);
5900                for e in elems {
5901                    match e {
5902                        TupleElemPattern::Pattern(Pattern::Int(lit)) => {
5903                            assert_eq!(lit.value, 0)
5904                        }
5905                        other => panic!("expected Int sub-pattern, got {:?}", other),
5906                    }
5907                }
5908            }
5909            other => panic!("expected Pattern::Tuple in match, got {:?}", other),
5910        }
5911    }
5912
5913    #[test]
5914    fn test_rest_in_tuple() {
5915        let source = "fn main() -> i32 { let (a, .., z) = quintuple(); 0 }";
5916        let result = parse_with_nested(source).unwrap();
5917        match find_first_let_pattern(&result) {
5918            Pattern::Tuple { elems, .. } => {
5919                assert_eq!(elems.len(), 3);
5920                assert!(matches!(&elems[1], TupleElemPattern::Rest(_)));
5921            }
5922            other => panic!("expected Pattern::Tuple, got {:?}", other),
5923        }
5924    }
5925
5926    #[test]
5927    fn test_rest_in_struct() {
5928        let source = "fn main() -> i32 { let Point { x, .. } = p(); 0 }";
5929        let result = parse_with_nested(source).unwrap();
5930        match find_first_let_pattern(&result) {
5931            Pattern::Struct { fields, .. } => {
5932                assert_eq!(fields.len(), 2);
5933                assert!(fields[0].field_name.is_some());
5934                assert!(fields[1].field_name.is_none()); // `..` sentinel
5935            }
5936            other => panic!("expected Pattern::Struct, got {:?}", other),
5937        }
5938    }
5939
5940    // Post-stabilization note (ADR-0049 Phase 8): the former
5941    // `test_preview_gate_rejects_*` tests checked that the nested-pattern
5942    // syntax was rejected without the preview flag. Nested patterns now
5943    // parse unconditionally, so those tests were removed. Coverage of the
5944    // parsed AST shapes lives in the `parse_with_nested` tests above.
5945    #[test]
5946    fn test_flat_tuple_destructure_still_parses() {
5947        // Flat pre-existing forms must continue to parse.
5948        let source = "fn main() -> i32 { let (a, b) = pair(); 0 }";
5949        let result = parse(source);
5950        assert!(
5951            result.is_ok(),
5952            "flat tuple destructure should parse unconditionally"
5953        );
5954    }
5955
5956    // ==================== ADR-0049 Phase 3: refutability ====================
5957
5958    fn assert_refutable_in_let_err(result: &MultiErrorResult<ParseResult>) {
5959        use gruel_util::ErrorKind;
5960        let errors = result.as_ref().expect_err("expected errors");
5961        let found = errors
5962            .iter()
5963            .any(|e| matches!(e.kind, ErrorKind::RefutablePatternInLet));
5964        assert!(
5965            found,
5966            "expected RefutablePatternInLet in: {:?}",
5967            errors
5968                .iter()
5969                .map(|e| e.kind.to_string())
5970                .collect::<Vec<_>>()
5971        );
5972    }
5973
5974    #[test]
5975    fn test_refutable_int_literal_in_let() {
5976        // `let 1 = x;` is refutable — literals match only one value.
5977        let source = "fn main() -> i32 { let 1 = 1; 0 }";
5978        assert_refutable_in_let_err(&parse(source));
5979    }
5980
5981    #[test]
5982    fn test_refutable_bool_in_let() {
5983        let source = "fn main() -> i32 { let true = true; 0 }";
5984        assert_refutable_in_let_err(&parse(source));
5985    }
5986
5987    #[test]
5988    fn test_refutable_enum_path_in_let() {
5989        let source = "fn main() -> i32 { let Color::Red = c; 0 }";
5990        assert_refutable_in_let_err(&parse(source));
5991    }
5992
5993    #[test]
5994    fn test_refutable_variant_in_let() {
5995        // `let Option::Some(x) = opt;` — refutable, even though the data-variant
5996        // pattern syntactically parses (stable after ADR-0049 Phase 8).
5997        let source = "fn main() -> i32 { let Option::Some(x) = opt(); 0 }";
5998        let result = parse_with_nested(source);
5999        assert_refutable_in_let_err(&result);
6000    }
6001
6002    #[test]
6003    fn test_refutable_nested_literal_in_let() {
6004        // `let (1, y) = t;` — the inner `1` makes the overall pattern refutable.
6005        let source = "fn main() -> i32 { let (1, y) = t(); 0 }";
6006        let result = parse_with_nested(source);
6007        assert_refutable_in_let_err(&result);
6008    }
6009
6010    #[test]
6011    fn test_irrefutable_tuple_in_let() {
6012        // All-irrefutable tuple is fine.
6013        let source = "fn main() -> i32 { let (a, b) = t(); 0 }";
6014        assert!(parse(source).is_ok());
6015    }
6016
6017    #[test]
6018    fn test_irrefutable_nested_struct_in_let() {
6019        let source = "fn main() -> i32 { let Outer { inner: Inner { x, y }, tag } = make(); 0 }";
6020        assert!(parse_with_nested(source).is_ok());
6021    }
6022
6023    // ========================================================================
6024    // ADR-0055: anonymous function expressions
6025    // ========================================================================
6026
6027    #[test]
6028    fn test_anon_fn_single_param_with_return() {
6029        let result = parse_expr("fn(x: i32) -> i32 { x + 1 }").unwrap();
6030        match &result.expr {
6031            Expr::AnonFn(anon) => {
6032                assert_eq!(anon.params.len(), 1);
6033                assert_eq!(result.get(anon.params[0].name.name), "x");
6034                assert!(anon.return_type.is_some());
6035            }
6036            other => panic!("expected AnonFn, got {:?}", other),
6037        }
6038    }
6039
6040    #[test]
6041    fn test_anon_fn_zero_param_no_return() {
6042        let result = parse_expr("fn() { 0 }").unwrap();
6043        match result.expr {
6044            Expr::AnonFn(anon) => {
6045                assert!(anon.params.is_empty());
6046                assert!(anon.return_type.is_none());
6047            }
6048            other => panic!("expected AnonFn, got {:?}", other),
6049        }
6050    }
6051
6052    #[test]
6053    fn test_anon_fn_multi_param() {
6054        let result = parse_expr("fn(x: i32, y: i32) -> i32 { x + y }").unwrap();
6055        match &result.expr {
6056            Expr::AnonFn(anon) => {
6057                assert_eq!(anon.params.len(), 2);
6058                assert_eq!(result.get(anon.params[0].name.name), "x");
6059                assert_eq!(result.get(anon.params[1].name.name), "y");
6060            }
6061            other => panic!("expected AnonFn, got {:?}", other),
6062        }
6063    }
6064
6065    #[test]
6066    fn test_anon_fn_in_call_arg() {
6067        // Anonymous function as a call argument, exercising the grammar inside
6068        // an argument list.
6069        let result = parse_expr("apply(fn(x: i32) -> i32 { x * 2 }, 3)").unwrap();
6070        match result.expr {
6071            Expr::Call(call) => {
6072                assert_eq!(call.args.len(), 2);
6073                assert!(matches!(call.args[0].expr, Expr::AnonFn(_)));
6074            }
6075            other => panic!("expected Call, got {:?}", other),
6076        }
6077    }
6078
6079    #[test]
6080    fn test_anon_fn_nested() {
6081        // An anonymous function whose body returns another anonymous function.
6082        let result = parse_expr("fn() { fn(x: i32) -> i32 { x } }").unwrap();
6083        match result.expr {
6084            Expr::AnonFn(outer) => {
6085                assert!(matches!(*outer.body.expr, Expr::AnonFn(_)));
6086            }
6087            other => panic!("expected AnonFn, got {:?}", other),
6088        }
6089    }
6090
6091    #[test]
6092    fn test_named_fn_at_top_level_still_works() {
6093        // Regression: adding `fn(` at expression position must not break the
6094        // `fn name(...)` item-level form.
6095        let result = parse("fn main() -> i32 { 0 }").unwrap();
6096        assert_eq!(result.ast.items.len(), 1);
6097        assert!(matches!(result.ast.items[0], Item::Function(_)));
6098    }
6099
6100    // ============================================================
6101    // ADR-0089: doc-comment attachment tests
6102    // ============================================================
6103
6104    fn parse_with_docs(source: &str) -> MultiErrorResult<ParseResult> {
6105        let lexer = Lexer::new(source);
6106        let (tokens, interner) = lexer.tokenize().map_err(CompileErrors::from)?;
6107        let parser = ChumskyParser::new(tokens, interner).with_source(source);
6108        let (ast, interner) = parser.parse()?;
6109        Ok(ParseResult { ast, interner })
6110    }
6111
6112    #[test]
6113    fn test_docs_dropped_when_source_not_provided() {
6114        // Without `.with_source()`, the parser has no LineIndex and
6115        // therefore drops doc tokens at the boundary.
6116        let source = "/// Module docs.\nfn main() -> i32 { 0 }";
6117        let result = parse(source).unwrap();
6118        assert!(result.ast.module_doc.is_none());
6119        match &result.ast.items[0] {
6120            Item::Function(f) => assert!(f.doc.is_none()),
6121            _ => panic!("expected Function"),
6122        }
6123    }
6124
6125    #[test]
6126    fn test_docs_on_attaches_glued_doc_to_function() {
6127        let source = "/// Docs for main.\nfn main() -> i32 { 0 }";
6128        let result = parse_with_docs(source).unwrap();
6129        assert!(result.ast.module_doc.is_none());
6130        match &result.ast.items[0] {
6131            Item::Function(f) => {
6132                let doc = f.doc.as_ref().expect("expected doc on main");
6133                assert_eq!(doc.body, "Docs for main.");
6134            }
6135            _ => panic!("expected Function"),
6136        }
6137    }
6138
6139    #[test]
6140    fn test_module_doc_separated_from_first_item() {
6141        let source = "/// Module docs.\n\nfn main() -> i32 { 0 }";
6142        let result = parse_with_docs(source).unwrap();
6143        let module_doc = result
6144            .ast
6145            .module_doc
6146            .as_ref()
6147            .expect("expected a module doc");
6148        assert_eq!(module_doc.body, "Module docs.");
6149        match &result.ast.items[0] {
6150            Item::Function(f) => assert!(f.doc.is_none()),
6151            _ => panic!("expected Function"),
6152        }
6153    }
6154
6155    #[test]
6156    fn test_module_doc_then_item_doc() {
6157        let source = "/// Module docs.\n\n/// Docs for main.\nfn main() -> i32 { 0 }";
6158        let result = parse_with_docs(source).unwrap();
6159        assert_eq!(result.ast.module_doc.as_ref().unwrap().body, "Module docs.");
6160        match &result.ast.items[0] {
6161            Item::Function(f) => {
6162                assert_eq!(f.doc.as_ref().unwrap().body, "Docs for main.");
6163            }
6164            _ => panic!("expected Function"),
6165        }
6166    }
6167
6168    #[test]
6169    fn test_doc_block_joins_consecutive_lines() {
6170        let source = "/// Line 1\n/// Line 2\nfn main() -> i32 { 0 }";
6171        let result = parse_with_docs(source).unwrap();
6172        match &result.ast.items[0] {
6173            Item::Function(f) => {
6174                assert_eq!(f.doc.as_ref().unwrap().body, "Line 1\nLine 2");
6175            }
6176            _ => panic!("expected Function"),
6177        }
6178    }
6179
6180    #[test]
6181    fn test_doc_after_other_item_attaches_to_main() {
6182        // First item has no docs above it; second item has a glued doc.
6183        // First block can't be module candidate because helper is above main's docs.
6184        let source = "fn helper() {}\n\n/// Docs for main.\nfn main() -> i32 { 0 }";
6185        let result = parse_with_docs(source).unwrap();
6186        assert!(result.ast.module_doc.is_none());
6187        match &result.ast.items[1] {
6188            Item::Function(f) => {
6189                assert_eq!(f.doc.as_ref().unwrap().body, "Docs for main.");
6190            }
6191            _ => panic!("expected Function at index 1"),
6192        }
6193    }
6194
6195    #[test]
6196    fn test_unglued_doc_after_first_item_is_error() {
6197        // Doc has an item above it (helper), and a blank line below before
6198        // the next item — disqualified as module candidate AND not glued.
6199        let source = "fn helper() {}\n\n/// Stray.\n\nfn main() -> i32 { 0 }";
6200        let result = parse_with_docs(source);
6201        assert!(result.is_err(), "expected parse error for orphan doc");
6202    }
6203
6204    #[test]
6205    fn test_doc_strips_one_leading_space() {
6206        // Lexer strips one space; the body should not have the leading space.
6207        let source = "/// hello\nfn main() -> i32 { 0 }";
6208        let result = parse_with_docs(source).unwrap();
6209        match &result.ast.items[0] {
6210            Item::Function(f) => assert_eq!(f.doc.as_ref().unwrap().body, "hello"),
6211            _ => panic!("expected Function"),
6212        }
6213    }
6214
6215    #[test]
6216    fn test_docs_on_struct_and_field() {
6217        let source = "\
6218/// Docs for Point.
6219struct Point {
6220    x: i32,
6221    y: i32,
6222}";
6223        let result = parse_with_docs(source).unwrap();
6224        match &result.ast.items[0] {
6225            Item::Struct(s) => {
6226                assert_eq!(s.doc.as_ref().unwrap().body, "Docs for Point.");
6227            }
6228            _ => panic!("expected Struct"),
6229        }
6230    }
6231}