Skip to main content

gruel_rir/
astgen.rs

1//! AST to RIR generation.
2//!
3//! AstGen converts the Abstract Syntax Tree into RIR instructions.
4//! This is analogous to Zig's AstGen phase.
5
6use lasso::{Spur, ThreadedRodeo};
7
8use gruel_intrinsics::{IntrinsicKind, is_type_intrinsic, lookup_by_name};
9use gruel_parser::ast::{
10    BlockExpr, ConstDecl, DeriveDecl, Directives, FieldPattern, Ident, SelfParam, SelfReceiverKind,
11    TupleElemPattern,
12};
13use gruel_parser::{
14    ArgMode, AssignTarget, Ast, BinaryOp as AstBinOp, CallArg, Directive, DirectiveArg, EnumDecl,
15    Expr, Function, IntrinsicArg, Item, Method, ParamMode, Pattern, Statement, StructDecl,
16    TypeExpr, UnaryOp as AstUnaryOp, ast::Visibility,
17};
18use gruel_util::{BinOp, UnaryOp};
19
20/// Encode a parsed receiver-kind classification as the byte the RIR (and
21/// downstream sema) consumes for `InterfaceMethodSig.receiver_mode` and
22/// `FnDecl.receiver_mode`.
23///
24/// Layout: 0 = by-value, 1 = `MutRef(Self)`, 2 = `Ref(Self)`. The numbering
25/// is preserved for cache-hash stability across the ADR-0076 cleanup.
26fn encode_self_receiver_kind(kind: SelfReceiverKind) -> u8 {
27    match kind {
28        SelfReceiverKind::ByValue => 0,
29        SelfReceiverKind::MutRef => 1,
30        SelfReceiverKind::Ref => 2,
31    }
32}
33
34use crate::inst::{
35    FunctionSpan, Inst, InstData, InstRef, Rir, RirArgMode, RirCallArg, RirDestructureField,
36    RirDirective, RirParam, RirParamMode, RirPattern, RirPatternBinding, RirStructField,
37    RirStructPatternBinding,
38};
39
40fn ast_binop_to_ir(op: AstBinOp) -> BinOp {
41    match op {
42        AstBinOp::Add => BinOp::Add,
43        AstBinOp::Sub => BinOp::Sub,
44        AstBinOp::Mul => BinOp::Mul,
45        AstBinOp::Div => BinOp::Div,
46        AstBinOp::Mod => BinOp::Mod,
47        AstBinOp::Eq => BinOp::Eq,
48        AstBinOp::Ne => BinOp::Ne,
49        AstBinOp::Lt => BinOp::Lt,
50        AstBinOp::Gt => BinOp::Gt,
51        AstBinOp::Le => BinOp::Le,
52        AstBinOp::Ge => BinOp::Ge,
53        AstBinOp::And => BinOp::And,
54        AstBinOp::Or => BinOp::Or,
55        AstBinOp::BitAnd => BinOp::BitAnd,
56        AstBinOp::BitOr => BinOp::BitOr,
57        AstBinOp::BitXor => BinOp::BitXor,
58        AstBinOp::Shl => BinOp::Shl,
59        AstBinOp::Shr => BinOp::Shr,
60    }
61}
62
63/// Generates RIR from an AST.
64pub struct AstGen<'a> {
65    /// The AST being processed
66    ast: &'a Ast,
67    /// String interner for symbols (thread-safe, takes shared reference)
68    interner: &'a ThreadedRodeo,
69    /// Output RIR
70    rir: Rir,
71    /// Counter for generating unique synthetic binding names used by the
72    /// refutable-nested-match elaborator for its intermediate scrutinee
73    /// locals. Shared by `fresh_nested_pat_name`.
74    nested_pat_counter: u32,
75}
76
77impl<'a> AstGen<'a> {
78    /// Create a new AstGen for the given AST.
79    pub fn new(ast: &'a Ast, interner: &'a ThreadedRodeo) -> Self {
80        Self {
81            ast,
82            interner,
83            rir: Rir::new(),
84            nested_pat_counter: 0,
85        }
86    }
87
88    /// Generate a fresh synthetic symbol name for an intermediate binding
89    /// used by `try_elaborate_refutable_nested_match` when rewriting
90    /// refutable-nested variant arms. Interned once and reused as both the
91    /// destructure-field binding name and the `VarRef` key.
92    fn fresh_nested_pat_name(&mut self) -> Spur {
93        let n = self.nested_pat_counter;
94        self.nested_pat_counter += 1;
95        self.interner.get_or_intern(format!("__nested_pat_{}", n))
96    }
97
98    /// Generate RIR from the AST.
99    pub fn generate(mut self) -> Rir {
100        for item in &self.ast.items {
101            self.gen_item(item);
102        }
103        self.rir
104    }
105
106    fn gen_item(&mut self, item: &Item) {
107        match item {
108            Item::Function(func) => {
109                self.gen_function(func);
110            }
111            Item::Struct(struct_decl) => {
112                self.gen_struct(struct_decl);
113            }
114            Item::Enum(enum_decl) => {
115                self.gen_enum(enum_decl);
116            }
117            Item::Interface(iface) => {
118                self.gen_interface(iface);
119            }
120            Item::Derive(derive_decl) => {
121                self.gen_derive(derive_decl);
122            }
123            Item::Const(const_decl) => {
124                self.gen_const(const_decl);
125            }
126            Item::LinkExtern(block) => {
127                self.gen_link_extern(block);
128            }
129            // Error nodes from parser recovery are skipped - errors were already reported
130            Item::Error(_) => {}
131        }
132    }
133
134    /// ADR-0085: lower a `link_extern("libname") { fn …; … }` block to a
135    /// set of [`RirExternFn`] entries on the side table.
136    fn gen_link_extern(&mut self, block: &gruel_parser::ast::LinkExternBlock) {
137        let link_mode = match block.link_mode {
138            gruel_parser::ast::LinkMode::Dynamic => crate::inst::RirLinkMode::Dynamic,
139            gruel_parser::ast::LinkMode::Static => crate::inst::RirLinkMode::Static,
140        };
141        if block.items.is_empty() {
142            // ADR-0085: empty blocks still contribute a library to the
143            // link line; track them so sema can validate the library
144            // name and codegen can emit `-l<lib>`.
145            self.rir
146                .add_empty_link_extern_block(block.library.value, link_mode, block.span);
147            return;
148        }
149        for item in &block.items {
150            let directives = self.convert_directives(&item.directives);
151            let (directives_start, directives_len) = self.rir.add_directives(&directives);
152
153            let params: Vec<_> = item
154                .params
155                .iter()
156                .map(|p| RirParam {
157                    name: p.name.name,
158                    ty: self.intern_type(&p.ty),
159                    mode: self.convert_param_mode(p.mode),
160                    is_comptime: p.is_comptime,
161                })
162                .collect();
163            let (params_start, params_len) = self.rir.add_params(&params);
164
165            let return_type = match &item.return_type {
166                Some(ty) => self.intern_type(ty),
167                None => self.interner.get_or_intern("()"),
168            };
169
170            self.rir.add_extern_fn(crate::inst::RirExternFn {
171                library: block.library.value,
172                name: item.name.name,
173                directives_start,
174                directives_len,
175                params_start,
176                params_len,
177                return_type,
178                span: item.span,
179                block_span: block.span,
180                link_mode,
181            });
182        }
183    }
184
185    /// ADR-0063: rendering helper for type-call args inside `AssocFnCall`.
186    ///
187    /// `Ptr(i32)::null()` carries `i32` as a regular `Expr` in the AST (the
188    /// parser doesn't know it's a type at parse time). At RIR-gen we render
189    /// it back into a string fragment so sema's existing
190    /// `parse_type_call_syntax` can re-parse the whole `Ptr(i32)` shape.
191    ///
192    /// Only the limited set of expression shapes that can sensibly appear as
193    /// a type argument are supported. Anything else stringifies to the
194    /// literal source name path; sema will still report the eventual
195    /// "unknown type" error if the argument doesn't resolve.
196    fn expr_as_type_name(&mut self, expr: &Expr) -> String {
197        match expr {
198            Expr::Ident(ident) => self.interner.resolve(&ident.name).to_string(),
199            Expr::TypeLit(lit) => {
200                let sym = self.intern_type(&lit.type_expr);
201                self.interner.resolve(&sym).to_string()
202            }
203            Expr::Call(call) => {
204                let mut s = String::new();
205                s.push_str(self.interner.resolve(&call.name.name));
206                s.push('(');
207                for (i, arg) in call.args.iter().enumerate() {
208                    if i > 0 {
209                        s.push_str(", ");
210                    }
211                    s.push_str(&self.expr_as_type_name(&arg.expr));
212                }
213                s.push(')');
214                s
215            }
216            other => format!("<unsupported type-arg: {:?}>", other),
217        }
218    }
219
220    /// Convert a TypeExpr to its symbol representation.
221    /// For named types, returns the existing symbol. For compound types, interns a new string.
222    fn intern_type(&mut self, ty: &TypeExpr) -> Spur {
223        match ty {
224            TypeExpr::Named(ident) => ident.name, // Already a Spur
225            TypeExpr::Unit(_) => self.interner.get_or_intern("()"),
226            TypeExpr::Never(_) => self.interner.get_or_intern("!"),
227            TypeExpr::Array {
228                element, length, ..
229            } => {
230                // For arrays, we need to construct a string representation
231                // Get the element symbol first, then look it up
232                let elem_sym = self.intern_type(element);
233                let elem_name = self.interner.resolve(&elem_sym);
234                let s = format!("[{}; {}]", elem_name, length);
235                self.interner.get_or_intern(&s)
236            }
237            TypeExpr::AnonymousStruct { fields, .. } => {
238                // For anonymous structs, generate a canonical name representation
239                let mut s = String::from("struct { ");
240                for (i, field) in fields.iter().enumerate() {
241                    if i > 0 {
242                        s.push_str(", ");
243                    }
244                    let name = self.interner.resolve(&field.name.name);
245                    let ty_sym = self.intern_type(&field.ty);
246                    let ty_name = self.interner.resolve(&ty_sym);
247                    s.push_str(name);
248                    s.push_str(": ");
249                    s.push_str(ty_name);
250                }
251                s.push_str(" }");
252                self.interner.get_or_intern(&s)
253            }
254            TypeExpr::AnonymousEnum { variants, .. } => {
255                // For anonymous enums, generate a canonical name representation
256                use gruel_parser::ast::EnumVariantKind;
257                let mut s = String::from("enum { ");
258                for (i, v) in variants.iter().enumerate() {
259                    if i > 0 {
260                        s.push_str(", ");
261                    }
262                    let name = self.interner.resolve(&v.name.name);
263                    s.push_str(name);
264                    match &v.kind {
265                        EnumVariantKind::Unit => {}
266                        EnumVariantKind::Tuple(types) => {
267                            s.push('(');
268                            for (j, ty) in types.iter().enumerate() {
269                                if j > 0 {
270                                    s.push_str(", ");
271                                }
272                                let ty_sym = self.intern_type(ty);
273                                s.push_str(self.interner.resolve(&ty_sym));
274                            }
275                            s.push(')');
276                        }
277                        EnumVariantKind::Struct(fields) => {
278                            s.push_str(" { ");
279                            for (j, f) in fields.iter().enumerate() {
280                                if j > 0 {
281                                    s.push_str(", ");
282                                }
283                                let fname = self.interner.resolve(&f.name.name);
284                                let ty_sym = self.intern_type(&f.ty);
285                                s.push_str(fname);
286                                s.push_str(": ");
287                                s.push_str(self.interner.resolve(&ty_sym));
288                            }
289                            s.push_str(" }");
290                        }
291                    }
292                }
293                s.push_str(" }");
294                self.interner.get_or_intern(&s)
295            }
296            TypeExpr::Tuple { elems, .. } => {
297                // Phase 1: just produce a canonical tuple name symbol.
298                // Phase 2 will lower tuples to anon structs with numeric field names.
299                let mut s = String::from("(");
300                for (i, elem) in elems.iter().enumerate() {
301                    if i > 0 {
302                        s.push_str(", ");
303                    }
304                    let elem_sym = self.intern_type(elem);
305                    s.push_str(self.interner.resolve(&elem_sym));
306                }
307                if elems.len() == 1 {
308                    s.push(',');
309                }
310                s.push(')');
311                self.interner.get_or_intern(&s)
312            }
313            TypeExpr::AnonymousInterface { methods, .. } => {
314                // ADR-0057: anonymous interfaces only appear inline in
315                // comptime type expressions (`interface { ... }`). The
316                // canonical name encodes method names so distinct shapes
317                // get distinct symbols.
318                let mut s = String::from("interface { ");
319                for (i, m) in methods.iter().enumerate() {
320                    if i > 0 {
321                        s.push_str(", ");
322                    }
323                    let mname = self.interner.resolve(&m.name.name);
324                    s.push_str(mname);
325                }
326                s.push_str(" }");
327                self.interner.get_or_intern(&s)
328            }
329            TypeExpr::TypeCall { callee, args, .. } => {
330                // ADR-0057: `Name(arg, ...)` in type position. The canonical
331                // name encodes the callee plus stringified args so distinct
332                // parameterizations get distinct symbols.
333                let mut s = String::from(self.interner.resolve(&callee.name));
334                s.push('(');
335                for (i, a) in args.iter().enumerate() {
336                    if i > 0 {
337                        s.push_str(", ");
338                    }
339                    let arg_sym = self.intern_type(a);
340                    s.push_str(self.interner.resolve(&arg_sym));
341                }
342                s.push(')');
343                self.interner.get_or_intern(&s)
344            }
345        }
346    }
347
348    fn gen_struct(&mut self, struct_decl: &StructDecl) -> InstRef {
349        let directives = self.convert_directives(&struct_decl.directives);
350        let (directives_start, directives_len) = self.rir.add_directives(&directives);
351        let name = struct_decl.name.name; // Already a Spur
352        let fields: Vec<_> = struct_decl
353            .fields
354            .iter()
355            .map(|f| {
356                let field_name = f.name.name; // Already a Spur
357                let field_type = self.intern_type(&f.ty);
358                let is_pub = f.visibility == gruel_parser::ast::Visibility::Public;
359                (field_name, field_type, is_pub)
360            })
361            .collect();
362        let (fields_start, fields_len) = self.rir.add_field_decls_with_vis(&fields);
363
364        // Generate each method defined inline in the struct
365        let methods: Vec<_> = struct_decl
366            .methods
367            .iter()
368            .map(|m| self.gen_method(m))
369            .collect();
370        let (methods_start, methods_len) = self.rir.add_inst_refs(&methods);
371
372        self.rir.add_inst(Inst {
373            data: InstData::StructDecl {
374                directives_start,
375                directives_len,
376                is_pub: struct_decl.visibility == Visibility::Public,
377                posture: struct_decl.posture,
378                name,
379                fields_start,
380                fields_len,
381                methods_start,
382                methods_len,
383            },
384            span: struct_decl.span,
385        })
386    }
387
388    fn gen_enum(&mut self, enum_decl: &EnumDecl) -> InstRef {
389        use gruel_parser::ast::EnumVariantKind;
390
391        let name = enum_decl.name.name; // Already a Spur
392        let variants: Vec<(Spur, Vec<Spur>, Vec<Spur>)> = enum_decl
393            .variants
394            .iter()
395            .map(|v| {
396                let variant_name = v.name.name;
397                match &v.kind {
398                    EnumVariantKind::Unit => (variant_name, vec![], vec![]),
399                    EnumVariantKind::Tuple(types) => {
400                        let field_types: Vec<Spur> =
401                            types.iter().map(|ty| self.intern_type(ty)).collect();
402                        (variant_name, field_types, vec![])
403                    }
404                    EnumVariantKind::Struct(fields) => {
405                        let field_types: Vec<Spur> =
406                            fields.iter().map(|f| self.intern_type(&f.ty)).collect();
407                        let field_names: Vec<Spur> = fields.iter().map(|f| f.name.name).collect();
408                        (variant_name, field_types, field_names)
409                    }
410                }
411            })
412            .collect();
413        let (variants_start, variants_len) = self.rir.add_enum_variant_decls(&variants);
414
415        // Generate each method defined inline in the enum (mirrors struct handling).
416        let methods: Vec<_> = enum_decl
417            .methods
418            .iter()
419            .map(|m| self.gen_method(m))
420            .collect();
421        let (methods_start, methods_len) = self.rir.add_inst_refs(&methods);
422
423        let directives = self.convert_directives(&enum_decl.directives);
424        let (directives_start, directives_len) = self.rir.add_directives(&directives);
425
426        self.rir.add_inst(Inst {
427            data: InstData::EnumDecl {
428                is_pub: enum_decl.visibility == Visibility::Public,
429                posture: enum_decl.posture,
430                name,
431                variants_start,
432                variants_len,
433                methods_start,
434                methods_len,
435                directives_start,
436                directives_len,
437            },
438            span: enum_decl.span,
439        })
440    }
441
442    fn gen_interface(&mut self, iface: &gruel_parser::ast::InterfaceDecl) -> InstRef {
443        use gruel_parser::ast::Visibility;
444
445        // Emit one InterfaceMethodSig instruction per declared method, then
446        // an InterfaceDecl that points to them via inst-refs.
447        let method_refs: Vec<InstRef> = iface
448            .methods
449            .iter()
450            .map(|sig| {
451                let name = sig.name.name;
452                let return_type = match &sig.return_type {
453                    Some(ty) => self.intern_type(ty),
454                    None => self.interner.get_or_intern("()"),
455                };
456                let params: Vec<_> = sig
457                    .params
458                    .iter()
459                    .map(|p| RirParam {
460                        name: p.name.name,
461                        ty: self.intern_type(&p.ty),
462                        mode: self.convert_param_mode(p.mode),
463                        is_comptime: p.is_comptime,
464                    })
465                    .collect();
466                let (params_start, params_len) = self.rir.add_params(&params);
467
468                let directives = self.convert_directives(&sig.directives);
469                let (directives_start, directives_len) = self.rir.add_directives(&directives);
470
471                let receiver_mode = encode_self_receiver_kind(sig.receiver.kind);
472                self.rir.add_inst(Inst {
473                    data: InstData::InterfaceMethodSig {
474                        name,
475                        params_start,
476                        params_len,
477                        return_type,
478                        receiver_mode,
479                        is_unchecked: sig.is_unchecked,
480                        directives_start,
481                        directives_len,
482                    },
483                    span: sig.span,
484                })
485            })
486            .collect();
487        let (methods_start, methods_len) = self.rir.add_inst_refs(&method_refs);
488
489        let directives = self.convert_directives(&iface.directives);
490        let (directives_start, directives_len) = self.rir.add_directives(&directives);
491
492        self.rir.add_inst(Inst {
493            data: InstData::InterfaceDecl {
494                is_pub: iface.visibility == Visibility::Public,
495                name: iface.name.name,
496                methods_start,
497                methods_len,
498                directives_start,
499                directives_len,
500            },
501            span: iface.span,
502        })
503    }
504
505    fn gen_derive(&mut self, derive_decl: &DeriveDecl) -> InstRef {
506        // Each method body is generated like an inline struct/enum method:
507        // `gen_method` emits a `FnDecl` instruction whose body uses `Self`
508        // as a free type variable, to be bound at derive-expansion time.
509        let method_refs: Vec<InstRef> = derive_decl
510            .methods
511            .iter()
512            .map(|m| self.gen_method(m))
513            .collect();
514        let (methods_start, methods_len) = self.rir.add_inst_refs(&method_refs);
515
516        self.rir.add_inst(Inst {
517            data: InstData::DeriveDecl {
518                name: derive_decl.name.name,
519                methods_start,
520                methods_len,
521            },
522            span: derive_decl.span,
523        })
524    }
525
526    fn gen_const(&mut self, const_decl: &ConstDecl) -> InstRef {
527        let directives = self.convert_directives(&const_decl.directives);
528        let (directives_start, directives_len) = self.rir.add_directives(&directives);
529        let name = const_decl.name.name; // Already a Spur
530        let ty = const_decl.ty.as_ref().map(|t| self.intern_type(t));
531        let init = self.gen_expr(&const_decl.init);
532
533        self.rir.add_inst(Inst {
534            data: InstData::ConstDecl {
535                directives_start,
536                directives_len,
537                is_pub: const_decl.visibility == Visibility::Public,
538                name,
539                ty,
540                init,
541            },
542            span: const_decl.span,
543        })
544    }
545
546    fn gen_method(&mut self, method: &Method) -> InstRef {
547        // Convert directives
548        let directives = self.convert_directives(&method.directives);
549        let (directives_start, directives_len) = self.rir.add_directives(&directives);
550
551        // Get the method name (already a Symbol) and return type
552        let name = method.name.name; // Already a Spur
553        let return_type = match &method.return_type {
554            Some(ty) => self.intern_type(ty),
555            None => self.interner.get_or_intern("()"), // Default to unit type
556        };
557
558        // Convert parameters (excluding self, which is handled specially by sema)
559        let params: Vec<_> = method
560            .params
561            .iter()
562            .map(|p| RirParam {
563                name: p.name.name, // Already a Spur
564                ty: self.intern_type(&p.ty),
565                mode: self.convert_param_mode(p.mode),
566                is_comptime: p.is_comptime,
567            })
568            .collect();
569        let (params_start, params_len) = self.rir.add_params(&params);
570
571        // Record the body start before generating body instructions
572        let body_start = InstRef::from_raw(self.rir.current_inst_index());
573
574        // Generate body expression
575        let body = self.gen_expr(&method.body);
576
577        // Track whether this method has a self receiver (method vs associated function)
578        let has_self = method.receiver.is_some();
579        let receiver_mode = method
580            .receiver
581            .as_ref()
582            .map(|r| encode_self_receiver_kind(r.kind))
583            .unwrap_or(0u8);
584
585        // Emit methods as FnDecl instructions with has_self flag.
586        // Sema uses has_self to add the implicit self parameter for methods.
587        // ADR-0073: methods carry their own `pub` flag, propagated from the
588        // parsed `Method::visibility`.
589        // ADR-0088: methods may carry `@mark(unchecked)`; the parser
590        // recognises the directive and sets `Method::is_unchecked`.
591        let is_pub = method.visibility == gruel_parser::ast::Visibility::Public;
592        let decl = self.rir.add_inst(Inst {
593            data: InstData::FnDecl {
594                directives_start,
595                directives_len,
596                is_pub,
597                is_unchecked: method.is_unchecked,
598                name,
599                params_start,
600                params_len,
601                return_type,
602                body,
603                has_self,
604                receiver_mode,
605            },
606            span: method.span,
607        });
608
609        // Record the function span for per-function analysis
610        // Methods are also tracked as functions for per-function analysis
611        self.rir
612            .add_function_span(FunctionSpan::new(name, body_start, decl));
613
614        decl
615    }
616
617    /// Convert AST directives to RIR directives
618    fn convert_directives(&mut self, directives: &[Directive]) -> Vec<RirDirective> {
619        directives
620            .iter()
621            .map(|d| RirDirective {
622                name: d.name.name, // Already a Spur
623                args: d
624                    .args
625                    .iter()
626                    .map(|arg| match arg {
627                        DirectiveArg::Ident(ident) => ident.name, // Already a Spur
628                        // Strings (ADR-0079: @lang("drop")) flatten to a
629                        // Spur for the same downstream handling as
630                        // identifier args.
631                        DirectiveArg::String(s) => s.value,
632                    })
633                    .collect(),
634                span: d.span,
635            })
636            .collect()
637    }
638
639    /// Convert AST ParamMode to RIR RirParamMode
640    fn convert_param_mode(&self, mode: ParamMode) -> RirParamMode {
641        match mode {
642            ParamMode::Normal => RirParamMode::Normal,
643            ParamMode::Comptime => RirParamMode::Comptime,
644        }
645    }
646
647    /// Convert AST ArgMode to RIR RirArgMode
648    fn convert_arg_mode(&self, mode: ArgMode) -> RirArgMode {
649        match mode {
650            ArgMode::Normal => RirArgMode::Normal,
651        }
652    }
653
654    /// Convert a CallArg to RirCallArg
655    fn convert_call_arg(&mut self, arg: &CallArg) -> RirCallArg {
656        RirCallArg {
657            value: self.gen_expr(&arg.expr),
658            mode: self.convert_arg_mode(arg.mode),
659        }
660    }
661
662    fn gen_function(&mut self, func: &Function) -> InstRef {
663        // Convert directives
664        let directives = self.convert_directives(&func.directives);
665        let (directives_start, directives_len) = self.rir.add_directives(&directives);
666
667        // Get the function name (already a Symbol) and return type
668        let name = func.name.name; // Already a Spur
669        let return_type = match &func.return_type {
670            Some(ty) => self.intern_type(ty),
671            None => self.interner.get_or_intern("()"), // Default to unit type
672        };
673
674        // Convert parameters
675        let params: Vec<_> = func
676            .params
677            .iter()
678            .map(|p| RirParam {
679                name: p.name.name, // Already a Spur
680                ty: self.intern_type(&p.ty),
681                mode: self.convert_param_mode(p.mode),
682                is_comptime: p.is_comptime,
683            })
684            .collect();
685        let (params_start, params_len) = self.rir.add_params(&params);
686
687        // Record the body start before generating body instructions
688        let body_start = InstRef::from_raw(self.rir.current_inst_index());
689
690        // Generate body expression
691        let body = self.gen_expr(&func.body);
692
693        // Create function declaration instruction
694        // Regular functions don't have a self receiver
695        let decl = self.rir.add_inst(Inst {
696            data: InstData::FnDecl {
697                directives_start,
698                directives_len,
699                is_pub: func.visibility == Visibility::Public,
700                is_unchecked: func.is_unchecked,
701                name,
702                params_start,
703                params_len,
704                return_type,
705                body,
706                has_self: false,
707                receiver_mode: 0,
708            },
709            span: func.span,
710        });
711
712        // Record the function span for per-function analysis
713        self.rir
714            .add_function_span(FunctionSpan::new(name, body_start, decl));
715
716        decl
717    }
718
719    fn gen_expr(&mut self, expr: &Expr) -> InstRef {
720        match expr {
721            Expr::Int(lit) => self.rir.add_inst(Inst {
722                data: InstData::IntConst(lit.value),
723                span: lit.span,
724            }),
725            Expr::Float(lit) => self.rir.add_inst(Inst {
726                data: InstData::FloatConst(lit.bits),
727                span: lit.span,
728            }),
729            Expr::Bool(lit) => self.rir.add_inst(Inst {
730                data: InstData::BoolConst(lit.value),
731                span: lit.span,
732            }),
733            Expr::Char(lit) => self.rir.add_inst(Inst {
734                data: InstData::CharConst(lit.value),
735                span: lit.span,
736            }),
737            Expr::String(lit) => {
738                self.rir.add_inst(Inst {
739                    data: InstData::StringConst(lit.value), // Already a Spur
740                    span: lit.span,
741                })
742            }
743            Expr::Unit(lit) => self.rir.add_inst(Inst {
744                data: InstData::UnitConst,
745                span: lit.span,
746            }),
747            Expr::Ident(ident) => {
748                self.rir.add_inst(Inst {
749                    data: InstData::VarRef { name: ident.name }, // Already a Spur
750                    span: ident.span,
751                })
752            }
753            Expr::Binary(bin) => {
754                let lhs = self.gen_expr(&bin.left);
755                let rhs = self.gen_expr(&bin.right);
756                let op = ast_binop_to_ir(bin.op);
757                self.rir.add_inst(Inst {
758                    data: InstData::Bin { op, lhs, rhs },
759                    span: bin.span,
760                })
761            }
762            Expr::Unary(un) => {
763                // ADR-0064: `&arr[range]` / `&mut arr[range]` are lowered to
764                // `MakeSlice`, not `MakeRef`. Detect the shape before emitting
765                // any instructions for the operand so that the range
766                // sub-expressions don't escape into a stand-alone `Range` IR
767                // node (there is none).
768                if matches!(un.op, AstUnaryOp::Ref | AstUnaryOp::MutRef)
769                    && let Expr::Index(index_expr) = &*un.operand
770                    && let Expr::Range(range_expr) = &*index_expr.index
771                {
772                    let base = self.gen_expr(&index_expr.base);
773                    let lo = range_expr.lo.as_ref().map(|e| self.gen_expr(e));
774                    let hi = range_expr.hi.as_ref().map(|e| self.gen_expr(e));
775                    let is_mut = matches!(un.op, AstUnaryOp::MutRef);
776                    return self.rir.add_inst(Inst {
777                        data: InstData::MakeSlice {
778                            base,
779                            lo,
780                            hi,
781                            is_mut,
782                        },
783                        span: un.span,
784                    });
785                }
786                let operand = self.gen_expr(&un.operand);
787                let data = match un.op {
788                    AstUnaryOp::Neg => InstData::Unary {
789                        op: UnaryOp::Neg,
790                        operand,
791                    },
792                    AstUnaryOp::Not => InstData::Unary {
793                        op: UnaryOp::Not,
794                        operand,
795                    },
796                    AstUnaryOp::BitNot => InstData::Unary {
797                        op: UnaryOp::BitNot,
798                        operand,
799                    },
800                    AstUnaryOp::Ref => InstData::MakeRef {
801                        operand,
802                        is_mut: false,
803                    },
804                    AstUnaryOp::MutRef => InstData::MakeRef {
805                        operand,
806                        is_mut: true,
807                    },
808                };
809                self.rir.add_inst(Inst {
810                    data,
811                    span: un.span,
812                })
813            }
814            Expr::Paren(paren) => {
815                // Parentheses are transparent in the IR - just generate the inner expression
816                self.gen_expr(&paren.inner)
817            }
818            Expr::Block(block) => self.gen_block(block),
819            Expr::If(if_expr) => {
820                let cond = self.gen_expr(&if_expr.cond);
821                let then_block = self.gen_block(&if_expr.then_block);
822                let else_block = if_expr.else_block.as_ref().map(|b| self.gen_block(b));
823
824                self.rir.add_inst(Inst {
825                    data: InstData::Branch {
826                        cond,
827                        then_block,
828                        else_block,
829                        is_comptime: if_expr.is_comptime,
830                    },
831                    span: if_expr.span,
832                })
833            }
834            Expr::While(while_expr) => {
835                let cond = self.gen_expr(&while_expr.cond);
836                let body = self.gen_block(&while_expr.body);
837                self.rir.add_inst(Inst {
838                    data: InstData::Loop { cond, body },
839                    span: while_expr.span,
840                })
841            }
842            Expr::For(for_expr) => {
843                let iterable = self.gen_expr(&for_expr.iterable);
844                let body = self.gen_block(&for_expr.body);
845                self.rir.add_inst(Inst {
846                    data: InstData::For {
847                        binding: for_expr.binding.name,
848                        is_mut: for_expr.is_mut,
849                        iterable,
850                        body,
851                    },
852                    span: for_expr.span,
853                })
854            }
855            Expr::Loop(loop_expr) => {
856                let body = self.gen_block(&loop_expr.body);
857                self.rir.add_inst(Inst {
858                    data: InstData::InfiniteLoop { body },
859                    span: loop_expr.span,
860                })
861            }
862            Expr::Match(match_expr) => {
863                // ADR-0051: single-arm irrefutable matches still rewrite to
864                // a let-destructure so drop ordering and field projections
865                // match `let pat = scr; body` semantics exactly.
866                if let Some(elaborated) = self.try_elaborate_irrefutable_match(match_expr) {
867                    return elaborated;
868                }
869
870                let scrutinee = self.gen_expr(&match_expr.scrutinee);
871                let mut arms: Vec<(RirPattern, InstRef)> =
872                    Vec::with_capacity(match_expr.arms.len());
873                for arm in &match_expr.arms {
874                    let mut nested: Vec<(Spur, Pattern)> = Vec::new();
875                    let pattern = self.gen_match_arm_pattern(&arm.pattern, &mut nested);
876                    let body = self.gen_expr(&arm.body);
877                    debug_assert!(
878                        nested.is_empty(),
879                        "ADR-0051: nested-pattern capture is superseded by RirPatternBinding.sub_pattern"
880                    );
881                    arms.push((pattern, body));
882                }
883                let (arms_start, arms_len) = self.rir.add_match_arms(&arms);
884
885                self.rir.add_inst(Inst {
886                    data: InstData::Match {
887                        scrutinee,
888                        arms_start,
889                        arms_len,
890                    },
891                    span: match_expr.span,
892                })
893            }
894            Expr::Call(call) => {
895                let args: Vec<_> = call.args.iter().map(|a| self.convert_call_arg(a)).collect();
896                let (args_start, args_len) = self.rir.add_call_args(&args);
897
898                self.rir.add_inst(Inst {
899                    data: InstData::Call {
900                        name: call.name.name, // Already a Spur
901                        args_start,
902                        args_len,
903                    },
904                    span: call.span,
905                })
906            }
907            Expr::Break(break_expr) => self.rir.add_inst(Inst {
908                data: InstData::Break,
909                span: break_expr.span,
910            }),
911            Expr::Continue(continue_expr) => self.rir.add_inst(Inst {
912                data: InstData::Continue,
913                span: continue_expr.span,
914            }),
915            Expr::Return(return_expr) => {
916                let value = return_expr.value.as_ref().map(|v| self.gen_expr(v));
917                self.rir.add_inst(Inst {
918                    data: InstData::Ret(value),
919                    span: return_expr.span,
920                })
921            }
922            Expr::StructLit(struct_lit) => {
923                // Generate module reference if this is a qualified struct literal
924                let module = struct_lit
925                    .base
926                    .as_ref()
927                    .map(|base_expr| self.gen_expr(base_expr));
928
929                let fields: Vec<_> = struct_lit
930                    .fields
931                    .iter()
932                    .map(|f| {
933                        let field_value = self.gen_expr(&f.value);
934                        (f.name.name, field_value) // name is already a Symbol
935                    })
936                    .collect();
937                let (fields_start, fields_len) = self.rir.add_field_inits(&fields);
938
939                self.rir.add_inst(Inst {
940                    data: InstData::StructInit {
941                        module,
942                        type_name: struct_lit.name.name, // Already a Spur
943                        fields_start,
944                        fields_len,
945                    },
946                    span: struct_lit.span,
947                })
948            }
949            Expr::EnumStructLit(lit) => {
950                let module = lit.base.as_ref().map(|base_expr| self.gen_expr(base_expr));
951
952                let fields: Vec<_> = lit
953                    .fields
954                    .iter()
955                    .map(|f| {
956                        let field_value = self.gen_expr(&f.value);
957                        (f.name.name, field_value)
958                    })
959                    .collect();
960                let (fields_start, fields_len) = self.rir.add_field_inits(&fields);
961
962                self.rir.add_inst(Inst {
963                    data: InstData::EnumStructVariant {
964                        module,
965                        type_name: lit.type_name.name,
966                        variant: lit.variant.name,
967                        fields_start,
968                        fields_len,
969                    },
970                    span: lit.span,
971                })
972            }
973            Expr::Field(field_expr) => {
974                let base = self.gen_expr(&field_expr.base);
975
976                self.rir.add_inst(Inst {
977                    data: InstData::FieldGet {
978                        base,
979                        field: field_expr.field.name, // Already a Spur
980                    },
981                    span: field_expr.span,
982                })
983            }
984            Expr::IntrinsicCall(intrinsic) => {
985                let name = intrinsic.name.name; // Already a Spur
986                let intrinsic_name_str = self.interner.resolve(&name);
987
988                let is_type = is_type_intrinsic(intrinsic_name_str);
989                let kind = lookup_by_name(intrinsic_name_str).map(|d| d.kind);
990
991                if is_type && intrinsic.args.len() == 1 {
992                    // Handle explicit type argument
993                    if let IntrinsicArg::Type(ty) = &intrinsic.args[0] {
994                        let type_arg = self.intern_type(ty);
995                        return self.rir.add_inst(Inst {
996                            data: InstData::TypeIntrinsic { name, type_arg },
997                            span: intrinsic.span,
998                        });
999                    }
1000
1001                    // Handle identifier expression that should be interpreted as a type
1002                    // (e.g., @size_of(Point) where Point is parsed as Ident expression)
1003                    if let IntrinsicArg::Expr(Expr::Ident(ident)) = &intrinsic.args[0] {
1004                        return self.rir.add_inst(Inst {
1005                            data: InstData::TypeIntrinsic {
1006                                name,
1007                                type_arg: ident.name, // Already a Spur
1008                            },
1009                            span: intrinsic.span,
1010                        });
1011                    }
1012                }
1013
1014                // Two-argument type+interface intrinsics like
1015                // `@implements(T, Iface)`. The interface argument
1016                // must be type-name-shaped (a bare identifier or
1017                // type expression). The type argument can also be a
1018                // comptime-evaluable expression (e.g.
1019                // `f.field_type` for a `f` from
1020                // `@type_info(Self).fields`); in that case we
1021                // gen_expr it and store the resulting `InstRef` on
1022                // `type_inst`, leaving `type_arg` as a placeholder
1023                // sentinel that sema ignores.
1024                if kind == Some(IntrinsicKind::TypeInterface) && intrinsic.args.len() == 2 {
1025                    let interface_arg = match &intrinsic.args[1] {
1026                        IntrinsicArg::Type(ty) => Some(self.intern_type(ty)),
1027                        IntrinsicArg::Expr(Expr::Ident(ident)) => Some(ident.name),
1028                        _ => None,
1029                    };
1030                    if let Some(interface_arg) = interface_arg {
1031                        let (type_arg, type_inst) = match &intrinsic.args[0] {
1032                            IntrinsicArg::Type(ty) => (self.intern_type(ty), None),
1033                            IntrinsicArg::Expr(Expr::Ident(ident)) => (ident.name, None),
1034                            IntrinsicArg::Expr(expr) => {
1035                                let inst = self.gen_expr(expr);
1036                                // Sentinel name; sema prefers
1037                                // type_inst when set so the value
1038                                // is unused.
1039                                (self.interner.get_or_intern_static("__expr__"), Some(inst))
1040                            }
1041                        };
1042                        return self.rir.add_inst(Inst {
1043                            data: InstData::TypeInterfaceIntrinsic {
1044                                name,
1045                                type_arg,
1046                                type_inst,
1047                                interface_arg,
1048                            },
1049                            span: intrinsic.span,
1050                        });
1051                    }
1052                }
1053
1054                // Otherwise, treat as an expression intrinsic.
1055                // ADR-0079 Phase 3: `@variant_uninit(T, tag)` and
1056                // `@variant_field(self, tag, name)` carry a `T` /
1057                // tag mix. For just those intrinsics, convert each
1058                // `IntrinsicArg::Type(...)` into a `TypeConst` inst
1059                // so it survives as a regular RIR arg (sema then
1060                // resolves it back to a type via
1061                // `resolve_intrinsic_type_arg`). The unambiguous-type
1062                // parser only matches primitive shapes (`i32`, `[T;N]`,
1063                // `Self`, etc.); a bare named-type identifier like
1064                // `Foo` parses as `IntrinsicArg::Expr(Expr::Ident)`,
1065                // so for these intrinsics we also promote a leading
1066                // bare identifier to `TypeConst` to keep the
1067                // type/value disambiguation at the call site rather
1068                // than forcing users to write `(Foo)` or similar.
1069                //
1070                // For every other expression intrinsic, dropping
1071                // `Type` args matches legacy behavior — the
1072                // surrounding HM inference resolves the target type
1073                // from context (e.g., `@cast(x, i32)`'s `i32` is
1074                // recovered via the resolved type at the call site).
1075                let preserve_type_args =
1076                    matches!(intrinsic_name_str, "variant_uninit" | "variant_field");
1077                // Only `@variant_uninit(T, tag)` puts the type in arg
1078                // position 0; `@variant_field(self, tag, name)`'s
1079                // first arg is a value, not a type.
1080                let promote_leading_ident_as_type = intrinsic_name_str == "variant_uninit";
1081                let args: Vec<_> = intrinsic
1082                    .args
1083                    .iter()
1084                    .enumerate()
1085                    .filter_map(|(idx, a)| match a {
1086                        IntrinsicArg::Type(ty) if preserve_type_args => {
1087                            let type_name = self.intern_type(ty);
1088                            Some(self.rir.add_inst(Inst {
1089                                data: InstData::TypeConst { type_name },
1090                                span: intrinsic.span,
1091                            }))
1092                        }
1093                        IntrinsicArg::Type(_) => None,
1094                        // Promote a leading bare-identifier to TypeConst
1095                        // for the type-carrying intrinsics so user code
1096                        // can write `@variant_uninit(Foo, "A")` without
1097                        // resorting to a type-expression dodge.
1098                        IntrinsicArg::Expr(Expr::Ident(ident))
1099                            if promote_leading_ident_as_type && idx == 0 =>
1100                        {
1101                            Some(self.rir.add_inst(Inst {
1102                                data: InstData::TypeConst {
1103                                    type_name: ident.name,
1104                                },
1105                                span: intrinsic.span,
1106                            }))
1107                        }
1108                        IntrinsicArg::Expr(expr) => Some(self.gen_expr(expr)),
1109                    })
1110                    .collect();
1111                let (args_start, args_len) = self.rir.add_inst_refs(&args);
1112
1113                self.rir.add_inst(Inst {
1114                    data: InstData::Intrinsic {
1115                        name,
1116                        args_start,
1117                        args_len,
1118                    },
1119                    span: intrinsic.span,
1120                })
1121            }
1122            Expr::ArrayLit(array_lit) => {
1123                let elements: Vec<_> = array_lit
1124                    .elements
1125                    .iter()
1126                    .map(|e| self.gen_expr(e))
1127                    .collect();
1128                let (elems_start, elems_len) = self.rir.add_inst_refs(&elements);
1129
1130                self.rir.add_inst(Inst {
1131                    data: InstData::ArrayInit {
1132                        elems_start,
1133                        elems_len,
1134                    },
1135                    span: array_lit.span,
1136                })
1137            }
1138            Expr::Index(index_expr) => {
1139                // ADR-0064: a range subscript without `&` / `&mut` is
1140                // rejected (no slice value without a borrow). Astgen
1141                // produces a tagged "bad subscript" instruction which sema
1142                // turns into a diagnostic; the borrow-form
1143                // `&arr[range]` is intercepted in `Expr::Unary` above and
1144                // never reaches this arm.
1145                if matches!(&*index_expr.index, Expr::Range(_)) {
1146                    return self.rir.add_inst(Inst {
1147                        data: InstData::BareRangeSubscript,
1148                        span: index_expr.span,
1149                    });
1150                }
1151                let base = self.gen_expr(&index_expr.base);
1152                let index = self.gen_expr(&index_expr.index);
1153
1154                self.rir.add_inst(Inst {
1155                    data: InstData::IndexGet { base, index },
1156                    span: index_expr.span,
1157                })
1158            }
1159            Expr::Path(path_expr) => {
1160                // Generate module reference if this is a qualified path
1161                let module = path_expr
1162                    .base
1163                    .as_ref()
1164                    .map(|base_expr| self.gen_expr(base_expr));
1165
1166                self.rir.add_inst(Inst {
1167                    data: InstData::EnumVariant {
1168                        module,
1169                        type_name: path_expr.type_name.name, // Already a Spur
1170                        variant: path_expr.variant.name,     // Already a Spur
1171                    },
1172                    span: path_expr.span,
1173                })
1174            }
1175            Expr::MethodCall(method_call) => {
1176                let receiver = self.gen_expr(&method_call.receiver);
1177                let args: Vec<_> = method_call
1178                    .args
1179                    .iter()
1180                    .map(|a| self.convert_call_arg(a))
1181                    .collect();
1182                let (args_start, args_len) = self.rir.add_call_args(&args);
1183
1184                self.rir.add_inst(Inst {
1185                    data: InstData::MethodCall {
1186                        receiver,
1187                        method: method_call.method.name, // Already a Spur
1188                        args_start,
1189                        args_len,
1190                    },
1191                    span: method_call.span,
1192                })
1193            }
1194            Expr::AssocFnCall(assoc_fn_call) => {
1195                let args: Vec<_> = assoc_fn_call
1196                    .args
1197                    .iter()
1198                    .map(|a| self.convert_call_arg(a))
1199                    .collect();
1200                let (args_start, args_len) = self.rir.add_call_args(&args);
1201
1202                // ADR-0063: when the LHS is a type-call (e.g. `Ptr(i32)::null()`),
1203                // synthesize a `Name(arg, arg, ...)` symbol so sema's existing
1204                // type-call resolution path picks up the fully-applied type.
1205                let type_name = if assoc_fn_call.type_args.is_empty() {
1206                    assoc_fn_call.type_name.name
1207                } else {
1208                    let mut s = String::new();
1209                    s.push_str(self.interner.resolve(&assoc_fn_call.type_name.name));
1210                    s.push('(');
1211                    for (i, arg) in assoc_fn_call.type_args.iter().enumerate() {
1212                        if i > 0 {
1213                            s.push_str(", ");
1214                        }
1215                        s.push_str(&self.expr_as_type_name(arg));
1216                    }
1217                    s.push(')');
1218                    self.interner.get_or_intern(&s)
1219                };
1220
1221                self.rir.add_inst(Inst {
1222                    data: InstData::AssocFnCall {
1223                        type_name,
1224                        function: assoc_fn_call.function.name, // Already a Spur
1225                        args_start,
1226                        args_len,
1227                    },
1228                    span: assoc_fn_call.span,
1229                })
1230            }
1231            Expr::SelfExpr(self_expr) => {
1232                // `self` in method bodies is just a variable reference to the implicit self parameter
1233                let name = self.interner.get_or_intern("self");
1234                self.rir.add_inst(Inst {
1235                    data: InstData::VarRef { name },
1236                    span: self_expr.span,
1237                })
1238            }
1239            Expr::Comptime(comptime_block) => {
1240                // Generate the inner expression, wrapped in a Comptime instruction
1241                // The semantic analyzer will evaluate this at compile time
1242                let inner_expr = self.gen_expr(&comptime_block.expr);
1243                self.rir.add_inst(Inst {
1244                    data: InstData::Comptime { expr: inner_expr },
1245                    span: comptime_block.span,
1246                })
1247            }
1248            Expr::ComptimeUnrollFor(unroll) => {
1249                let iterable = self.gen_expr(&unroll.iterable);
1250                let body = self.gen_block(&unroll.body);
1251                self.rir.add_inst(Inst {
1252                    data: InstData::ComptimeUnrollFor {
1253                        binding: unroll.binding.name,
1254                        iterable,
1255                        body,
1256                    },
1257                    span: unroll.span,
1258                })
1259            }
1260            Expr::Checked(checked_block) => {
1261                // Generate the inner expression, wrapped in a Checked instruction
1262                // Unchecked operations are only allowed inside checked blocks
1263                let inner_expr = self.gen_expr(&checked_block.expr);
1264                self.rir.add_inst(Inst {
1265                    data: InstData::Checked { expr: inner_expr },
1266                    span: checked_block.span,
1267                })
1268            }
1269            Expr::TypeLit(type_lit) => {
1270                // Generate a type constant instruction for type-as-value expressions
1271                match &type_lit.type_expr {
1272                    TypeExpr::AnonymousStruct {
1273                        directives,
1274                        fields,
1275                        methods,
1276                        ..
1277                    } => {
1278                        // Generate an anonymous struct type instruction with methods
1279                        let rir_directives = self.convert_directives(directives);
1280                        let (directives_start, directives_len) =
1281                            self.rir.add_directives(&rir_directives);
1282                        // Anonymous struct fields are always treated as public:
1283                        // anonymous types are structurally typed and have no
1284                        // owning module, so module-scoped visibility doesn't
1285                        // apply (ADR-0073).
1286                        let field_decls: Vec<(Spur, Spur, bool)> = fields
1287                            .iter()
1288                            .map(|f| {
1289                                let name = f.name.name;
1290                                let ty = self.intern_type(&f.ty);
1291                                (name, ty, true)
1292                            })
1293                            .collect();
1294                        let (fields_start, fields_len) =
1295                            self.rir.add_field_decls_with_vis(&field_decls);
1296
1297                        // Generate each method inside the anonymous struct
1298                        // (reusing gen_method, which generates FnDecl instructions)
1299                        let method_refs: Vec<InstRef> =
1300                            methods.iter().map(|m| self.gen_method(m)).collect();
1301                        let (methods_start, methods_len) = self.rir.add_inst_refs(&method_refs);
1302
1303                        self.rir.add_inst(Inst {
1304                            data: InstData::AnonStructType {
1305                                directives_start,
1306                                directives_len,
1307                                fields_start,
1308                                fields_len,
1309                                methods_start,
1310                                methods_len,
1311                            },
1312                            span: type_lit.span,
1313                        })
1314                    }
1315                    TypeExpr::AnonymousInterface { methods, .. } => {
1316                        // ADR-0057: build an `AnonInterfaceType` instruction
1317                        // carrying one `InterfaceMethodSig` per declared
1318                        // method. Mirrors the gen_interface flow for named
1319                        // interfaces but without the enclosing
1320                        // `InterfaceDecl`.
1321                        let method_refs: Vec<InstRef> = methods
1322                            .iter()
1323                            .map(|sig| {
1324                                let name = sig.name.name;
1325                                let return_type = match &sig.return_type {
1326                                    Some(ty) => self.intern_type(ty),
1327                                    None => self.interner.get_or_intern("()"),
1328                                };
1329                                let params: Vec<_> = sig
1330                                    .params
1331                                    .iter()
1332                                    .map(|p| RirParam {
1333                                        name: p.name.name,
1334                                        ty: self.intern_type(&p.ty),
1335                                        mode: self.convert_param_mode(p.mode),
1336                                        is_comptime: p.is_comptime,
1337                                    })
1338                                    .collect();
1339                                let (params_start, params_len) = self.rir.add_params(&params);
1340                                let directives = self.convert_directives(&sig.directives);
1341                                let (directives_start, directives_len) =
1342                                    self.rir.add_directives(&directives);
1343                                let receiver_mode = encode_self_receiver_kind(sig.receiver.kind);
1344                                self.rir.add_inst(Inst {
1345                                    data: InstData::InterfaceMethodSig {
1346                                        name,
1347                                        params_start,
1348                                        params_len,
1349                                        return_type,
1350                                        receiver_mode,
1351                                        is_unchecked: sig.is_unchecked,
1352                                        directives_start,
1353                                        directives_len,
1354                                    },
1355                                    span: sig.span,
1356                                })
1357                            })
1358                            .collect();
1359                        let (methods_start, methods_len) = self.rir.add_inst_refs(&method_refs);
1360                        self.rir.add_inst(Inst {
1361                            data: InstData::AnonInterfaceType {
1362                                methods_start,
1363                                methods_len,
1364                            },
1365                            span: type_lit.span,
1366                        })
1367                    }
1368                    TypeExpr::AnonymousEnum {
1369                        directives,
1370                        variants,
1371                        methods,
1372                        ..
1373                    } => {
1374                        // Generate an anonymous enum type instruction with methods
1375                        use gruel_parser::ast::EnumVariantKind;
1376                        let rir_directives = self.convert_directives(directives);
1377                        let (directives_start, directives_len) =
1378                            self.rir.add_directives(&rir_directives);
1379                        let variant_decls: Vec<(Spur, Vec<Spur>, Vec<Spur>)> = variants
1380                            .iter()
1381                            .map(|v| {
1382                                let variant_name = v.name.name;
1383                                match &v.kind {
1384                                    EnumVariantKind::Unit => (variant_name, vec![], vec![]),
1385                                    EnumVariantKind::Tuple(types) => {
1386                                        let field_types: Vec<Spur> =
1387                                            types.iter().map(|ty| self.intern_type(ty)).collect();
1388                                        (variant_name, field_types, vec![])
1389                                    }
1390                                    EnumVariantKind::Struct(fields) => {
1391                                        let field_types: Vec<Spur> = fields
1392                                            .iter()
1393                                            .map(|f| self.intern_type(&f.ty))
1394                                            .collect();
1395                                        let field_names: Vec<Spur> =
1396                                            fields.iter().map(|f| f.name.name).collect();
1397                                        (variant_name, field_types, field_names)
1398                                    }
1399                                }
1400                            })
1401                            .collect();
1402                        let (variants_start, variants_len) =
1403                            self.rir.add_enum_variant_decls(&variant_decls);
1404
1405                        // Generate each method inside the anonymous enum
1406                        let method_refs: Vec<InstRef> =
1407                            methods.iter().map(|m| self.gen_method(m)).collect();
1408                        let (methods_start, methods_len) = self.rir.add_inst_refs(&method_refs);
1409
1410                        self.rir.add_inst(Inst {
1411                            data: InstData::AnonEnumType {
1412                                directives_start,
1413                                directives_len,
1414                                variants_start,
1415                                variants_len,
1416                                methods_start,
1417                                methods_len,
1418                            },
1419                            span: type_lit.span,
1420                        })
1421                    }
1422                    _ => {
1423                        // For named types, unit, never, arrays, and pointers, generate TypeConst
1424                        let type_name = match &type_lit.type_expr {
1425                            TypeExpr::Named(ident) => ident.name,
1426                            TypeExpr::Unit(_) => self.interner.get_or_intern_static("()"),
1427                            TypeExpr::Never(_) => self.interner.get_or_intern_static("!"),
1428                            TypeExpr::Array { .. } => {
1429                                // Array types as values are not yet supported
1430                                // For now, use a placeholder
1431                                self.interner.get_or_intern_static("array")
1432                            }
1433                            TypeExpr::AnonymousStruct { .. }
1434                            | TypeExpr::AnonymousEnum { .. }
1435                            | TypeExpr::AnonymousInterface { .. } => {
1436                                unreachable!("handled above")
1437                            }
1438                            TypeExpr::TypeCall { .. } => {
1439                                // ADR-0057: parameterized type call as a
1440                                // type literal. Route through intern_type;
1441                                // sema's `resolve_type` evaluates the call
1442                                // at comptime when the symbol is consumed.
1443                                self.intern_type(&type_lit.type_expr)
1444                            }
1445                            TypeExpr::Tuple { .. } => {
1446                                // Phase 1: route through intern_type. Phase 2 will lower
1447                                // tuples to anon structs and handle type-value use properly.
1448                                self.intern_type(&type_lit.type_expr)
1449                            }
1450                        };
1451                        self.rir.add_inst(Inst {
1452                            data: InstData::TypeConst { type_name },
1453                            span: type_lit.span,
1454                        })
1455                    }
1456                }
1457            }
1458            // Error nodes from parser recovery - generate a unit constant as a placeholder
1459            // The error was already reported during parsing
1460            Expr::Error(span) => self.rir.add_inst(Inst {
1461                data: InstData::UnitConst,
1462                span: *span,
1463            }),
1464            // Tuple literal `(e0, e1, ...)` — lowered in sema to an anon struct
1465            // with field names "0", "1", ... (ADR-0048).
1466            Expr::Tuple(tuple) => {
1467                let elem_refs: Vec<InstRef> =
1468                    tuple.elems.iter().map(|e| self.gen_expr(e)).collect();
1469                let elem_u32s: Vec<u32> = elem_refs.iter().map(|r| r.as_u32()).collect();
1470                let elems_start = self.rir.add_extra(&elem_u32s);
1471                let elems_len = elem_refs.len() as u32;
1472                self.rir.add_inst(Inst {
1473                    data: InstData::TupleInit {
1474                        elems_start,
1475                        elems_len,
1476                    },
1477                    span: tuple.span,
1478                })
1479            }
1480            // Tuple index `t.N` — lowered to a regular FieldGet whose field symbol
1481            // is the stringified index. Synthetic names cannot collide with user
1482            // struct field names (which must start with a letter).
1483            Expr::TupleIndex(ti) => {
1484                let base = self.gen_expr(&ti.base);
1485                let field = self.interner.get_or_intern(ti.index.to_string());
1486                self.rir.add_inst(Inst {
1487                    data: InstData::FieldGet { base, field },
1488                    span: ti.span,
1489                })
1490            }
1491            // Anonymous function expression (ADR-0055): desugar to a lambda-
1492            // origin anonymous struct with one `__call` method, and produce an
1493            // empty instance of it.
1494            //
1495            // Strategy: synthesize a `Method { name: "__call", receiver: self,
1496            // params, return_type, body }`, run it through the normal
1497            // `gen_method` to get a FnDecl InstRef, then emit `AnonFnValue`
1498            // referencing that method. Sema creates the actual struct type
1499            // and instantiates it; Phase 3 makes each site unique.
1500            Expr::AnonFn(anon_fn) => {
1501                let call_name_sym = self.interner.get_or_intern_static("__call");
1502                let call_ident = Ident {
1503                    name: call_name_sym,
1504                    span: anon_fn.span,
1505                };
1506                let synth_method = Method {
1507                    doc: None,
1508                    directives: Directives::new(),
1509                    visibility: gruel_parser::ast::Visibility::Public,
1510                    is_unchecked: false,
1511                    name: call_ident,
1512                    receiver: Some(SelfParam {
1513                        kind: gruel_parser::ast::SelfReceiverKind::ByValue,
1514                        span: anon_fn.span,
1515                    }),
1516                    params: anon_fn.params.clone(),
1517                    return_type: anon_fn.return_type.clone(),
1518                    body: Expr::Block(BlockExpr {
1519                        statements: anon_fn.body.statements.clone(),
1520                        expr: anon_fn.body.expr.clone(),
1521                        span: anon_fn.body.span,
1522                    }),
1523                    span: anon_fn.span,
1524                };
1525                let method_ref = self.gen_method(&synth_method);
1526                self.rir.add_inst(Inst {
1527                    data: InstData::AnonFnValue { method: method_ref },
1528                    span: anon_fn.span,
1529                })
1530            }
1531            // ADR-0064: stray range expressions outside of `arr[..]`
1532            // subscript position. The parser only emits `Expr::Range` as the
1533            // index of an `Expr::Index`; reaching this arm means the parser
1534            // was extended without updating astgen.
1535            Expr::Range(range_expr) => self.rir.add_inst(Inst {
1536                data: InstData::BareRangeSubscript,
1537                span: range_expr.span,
1538            }),
1539        }
1540    }
1541
1542    /// Elaborate a match expression whose single arm has an irrefutable
1543    /// top-level pattern into a block containing a let-destructure over the
1544    /// scrutinee plus the arm body (ADR-0049 Phase 4b).
1545    ///
1546    /// Covers `match x { name => body }`, `match p { Point { x, y } => body }`,
1547    /// and `match t { (a, b) => body }` — single-arm matches where the pattern
1548    /// binds the scrutinee without branching. For multi-arm or refutable
1549    /// top-level patterns, returns `None` and the caller falls back to the
1550    /// normal match lowering.
1551    fn try_elaborate_irrefutable_match(
1552        &mut self,
1553        match_expr: &gruel_parser::MatchExpr,
1554    ) -> Option<InstRef> {
1555        if match_expr.arms.len() != 1 {
1556            return None;
1557        }
1558        let arm = &match_expr.arms[0];
1559        match &arm.pattern {
1560            Pattern::Ident { is_mut, name, span } => {
1561                // `match x { name => body }` => `{ let name = x; body }`
1562                let init = self.gen_expr(&match_expr.scrutinee);
1563                let alloc = self.rir.add_inst(Inst {
1564                    data: InstData::Alloc {
1565                        directives_start: 0,
1566                        directives_len: 0,
1567                        name: Some(name.name),
1568                        is_mut: *is_mut,
1569                        ty: None,
1570                        init,
1571                    },
1572                    span: *span,
1573                });
1574                let body = self.gen_expr(&arm.body);
1575                let extra_start = self.rir.add_extra(&[alloc.as_u32(), body.as_u32()]);
1576                Some(self.rir.add_inst(Inst {
1577                    data: InstData::Block {
1578                        extra_start,
1579                        len: 2,
1580                    },
1581                    span: match_expr.span,
1582                }))
1583            }
1584            Pattern::Struct { .. } | Pattern::Tuple { .. }
1585                if is_irrefutable_destructure(&arm.pattern) =>
1586            {
1587                // `match x { <pattern> => body }` => `{ let <pattern> = x; body }`
1588                let init = self.gen_expr(&match_expr.scrutinee);
1589                let mut stmts: Vec<u32> = Vec::new();
1590                let mut emitted = Vec::new();
1591                self.emit_let_destructure_into(
1592                    &arm.pattern,
1593                    init,
1594                    arm.pattern.span(),
1595                    &mut emitted,
1596                );
1597                for r in emitted {
1598                    stmts.push(r.as_u32());
1599                }
1600                let body = self.gen_expr(&arm.body);
1601                stmts.push(body.as_u32());
1602                let extra_start = self.rir.add_extra(&stmts);
1603                let len = stmts.len() as u32;
1604                Some(self.rir.add_inst(Inst {
1605                    data: InstData::Block { extra_start, len },
1606                    span: match_expr.span,
1607                }))
1608            }
1609            _ => None,
1610        }
1611    }
1612
1613    /// Lower a match-arm pattern to RIR, collecting nested sub-patterns for
1614    /// body elaboration (ADR-0049 Phase 4b).
1615    ///
1616    /// When a variant field carries an irrefutable nested destructure
1617    /// (`Some((a, b))`, `Some(Point { x, y })`), the sub-pattern is replaced
1618    /// with a fresh synthetic binding in the RIR pattern, and the
1619    /// `(fresh_name, sub_pattern)` pair is appended to `nested`. The caller
1620    /// prepends a `let <sub_pattern> = <fresh_name>;` to the arm body so the
1621    /// user's bindings come into scope.
1622    fn gen_match_arm_pattern(
1623        &mut self,
1624        pattern: &Pattern,
1625        nested: &mut Vec<(Spur, Pattern)>,
1626    ) -> RirPattern {
1627        match pattern {
1628            Pattern::Wildcard(span) => RirPattern::Wildcard(*span),
1629            Pattern::Int(lit) => RirPattern::Int(lit.value as i64, lit.span),
1630            // Use wrapping_neg to handle i64::MIN correctly (where value is 9223372036854775808)
1631            Pattern::NegInt(lit) => RirPattern::Int((lit.value as i64).wrapping_neg(), lit.span),
1632            Pattern::Bool(lit) => RirPattern::Bool(lit.value, lit.span),
1633            Pattern::Path(path) => {
1634                // If there's a base expression (module reference), generate it first
1635                let module = path.base.as_ref().map(|base| self.gen_expr(base));
1636                RirPattern::Path {
1637                    module,
1638                    type_name: path.type_name.name, // Already a Spur
1639                    variant: path.variant.name,     // Already a Spur
1640                    span: path.span,
1641                }
1642            }
1643            Pattern::DataVariant {
1644                base,
1645                type_name,
1646                variant,
1647                fields,
1648                span,
1649            } => {
1650                let module = base.as_ref().map(|b| self.gen_expr(b));
1651                let rir_bindings = fields
1652                    .iter()
1653                    .map(|elem| self.tuple_elem_to_rir_binding_or_capture(elem, nested))
1654                    .collect();
1655                RirPattern::DataVariant {
1656                    module,
1657                    type_name: type_name.name,
1658                    variant: variant.name,
1659                    bindings: rir_bindings,
1660                    span: *span,
1661                }
1662            }
1663            Pattern::StructVariant {
1664                base,
1665                type_name,
1666                variant,
1667                fields,
1668                span,
1669            } => {
1670                let module = base.as_ref().map(|b| self.gen_expr(b));
1671                let rest_marker = self.interner.get_or_intern_static("..");
1672                let field_bindings = fields
1673                    .iter()
1674                    .map(|fb| {
1675                        // For `..` rest patterns, synthesize a field_name of
1676                        // the rest-marker sentinel so sema can detect it
1677                        // (ADR-0049 Phase 6).
1678                        let field_name = fb
1679                            .field_name
1680                            .as_ref()
1681                            .map(|ident| ident.name)
1682                            .unwrap_or(rest_marker);
1683                        RirStructPatternBinding {
1684                            field_name,
1685                            binding: self.field_pattern_to_rir_binding_or_capture(fb, nested),
1686                        }
1687                    })
1688                    .collect();
1689                RirPattern::StructVariant {
1690                    module,
1691                    type_name: type_name.name,
1692                    variant: variant.name,
1693                    field_bindings,
1694                    span: *span,
1695                }
1696            }
1697            // Top-level Struct / Tuple / Ident match arms — sema + CFG
1698            // consume them directly as `RirPattern::Tuple` / `Struct` /
1699            // `Ident`, recursively lowering sub-patterns along the way.
1700            Pattern::Ident { name, is_mut, span } => {
1701                let _ = nested;
1702                RirPattern::Ident {
1703                    name: name.name,
1704                    is_mut: *is_mut,
1705                    span: *span,
1706                }
1707            }
1708            Pattern::Tuple { elems, span } => {
1709                // Record the source index of any `..` rest marker so sema
1710                // can expand it to wildcards filling the scrutinee's arity
1711                // (ADR-0049 Phase 6 semantics preserved). The marker is
1712                // stripped from the RIR elems list and reconstructed in
1713                // sema.
1714                let mut rir_elems: Vec<RirPattern> = Vec::with_capacity(elems.len());
1715                let mut rest_position: Option<u32> = None;
1716                for (i, e) in elems.iter().enumerate() {
1717                    match e {
1718                        TupleElemPattern::Pattern(p) => {
1719                            rir_elems.push(self.gen_match_arm_pattern(p, nested));
1720                        }
1721                        TupleElemPattern::Rest(_) => {
1722                            rest_position = Some(i as u32);
1723                        }
1724                    }
1725                }
1726                RirPattern::Tuple {
1727                    elems: rir_elems,
1728                    rest_position,
1729                    span: *span,
1730                }
1731            }
1732            Pattern::Struct {
1733                type_name,
1734                fields,
1735                span,
1736            } => {
1737                // AST represents `..` as a `FieldPattern` with
1738                // `field_name = None`; capture that as a top-level
1739                // `has_rest` boolean and drop those fields from the
1740                // recursive-RIR field list.
1741                let has_rest = fields.iter().any(|f| f.field_name.is_none());
1742                let rir_fields: Vec<RirStructField> = fields
1743                    .iter()
1744                    .filter_map(|f| {
1745                        let field_name = f.field_name.as_ref()?.name;
1746                        let pat = match &f.sub {
1747                            Some(p) => self.gen_match_arm_pattern(p, nested),
1748                            None => {
1749                                // Shorthand `{ x }` or `{ mut x }`: bind the
1750                                // field to a local with the same name.
1751                                RirPattern::Ident {
1752                                    name: field_name,
1753                                    is_mut: f.is_mut,
1754                                    span: f.span,
1755                                }
1756                            }
1757                        };
1758                        Some(RirStructField {
1759                            field_name,
1760                            pattern: pat,
1761                        })
1762                    })
1763                    .collect();
1764                RirPattern::Struct {
1765                    module: None,
1766                    type_name: type_name.name,
1767                    fields: rir_fields,
1768                    has_rest,
1769                    span: *span,
1770                }
1771            }
1772            Pattern::ComptimeUnrollArm {
1773                binding,
1774                iterable,
1775                span,
1776            } => {
1777                let _ = nested;
1778                let iterable_ref = self.gen_expr(iterable);
1779                RirPattern::ComptimeUnrollArm {
1780                    binding: binding.name,
1781                    iterable: iterable_ref,
1782                    span: *span,
1783                }
1784            }
1785        }
1786    }
1787
1788    /// Convert a match-arm tuple-element sub-pattern into the RIR binding
1789    /// shape used by `DataVariant`. ADR-0051: any refutable/irrefutable
1790    /// nested sub-pattern is stored directly on the binding's `sub_pattern`
1791    /// slot; only bare names and wildcards produce flat bindings.
1792    fn tuple_elem_to_rir_binding_or_capture(
1793        &mut self,
1794        elem: &TupleElemPattern,
1795        nested: &mut Vec<(Spur, Pattern)>,
1796    ) -> RirPatternBinding {
1797        match elem {
1798            TupleElemPattern::Pattern(Pattern::Wildcard(_)) => RirPatternBinding {
1799                is_wildcard: true,
1800                is_mut: false,
1801                name: None,
1802                sub_pattern: None,
1803            },
1804            TupleElemPattern::Pattern(Pattern::Ident { is_mut, name, .. }) => RirPatternBinding {
1805                is_wildcard: false,
1806                is_mut: *is_mut,
1807                name: Some(name.name),
1808                sub_pattern: None,
1809            },
1810            TupleElemPattern::Pattern(sub) => {
1811                // Any other sub-pattern (refutable or otherwise) rides on
1812                // the binding's nested sub_pattern slot. `gen_match_arm_pattern`
1813                // recursively turns AST into RIR.
1814                let sub_rir = self.gen_match_arm_pattern(sub, nested);
1815                RirPatternBinding {
1816                    is_wildcard: false,
1817                    is_mut: false,
1818                    name: None,
1819                    sub_pattern: Some(Box::new(sub_rir)),
1820                }
1821            }
1822            TupleElemPattern::Rest(_) => {
1823                // `..` in a data-variant pattern: emit a marker binding
1824                // with the sentinel name `..`. Sema detects this marker
1825                // and expands it to wildcards for the remaining variant
1826                // fields (ADR-0049 Phase 6).
1827                RirPatternBinding {
1828                    is_wildcard: true,
1829                    is_mut: false,
1830                    name: Some(self.interner.get_or_intern_static("..")),
1831                    sub_pattern: None,
1832                }
1833            }
1834        }
1835    }
1836
1837    /// Convert a match-arm struct-variant field-pattern into the RIR binding
1838    /// shape. Same rules as `tuple_elem_to_rir_binding_or_capture`.
1839    fn field_pattern_to_rir_binding_or_capture(
1840        &mut self,
1841        fb: &FieldPattern,
1842        nested: &mut Vec<(Spur, Pattern)>,
1843    ) -> RirPatternBinding {
1844        // `..` in a struct-variant pattern: emit a marker binding with the
1845        // sentinel name `..`. Sema recognizes it as a rest and skips the
1846        // missing-fields check (ADR-0049 Phase 6).
1847        let Some(name) = fb.field_name.as_ref() else {
1848            return RirPatternBinding {
1849                is_wildcard: true,
1850                is_mut: false,
1851                name: Some(self.interner.get_or_intern_static("..")),
1852                sub_pattern: None,
1853            };
1854        };
1855        match &fb.sub {
1856            None => RirPatternBinding {
1857                // Shorthand: `field` binds a local named `field`.
1858                is_wildcard: false,
1859                is_mut: fb.is_mut,
1860                name: Some(name.name),
1861                sub_pattern: None,
1862            },
1863            Some(Pattern::Wildcard(_)) => RirPatternBinding {
1864                is_wildcard: true,
1865                is_mut: false,
1866                name: None,
1867                sub_pattern: None,
1868            },
1869            Some(Pattern::Ident {
1870                is_mut,
1871                name: bind_name,
1872                ..
1873            }) => RirPatternBinding {
1874                is_wildcard: false,
1875                is_mut: fb.is_mut || *is_mut,
1876                name: Some(bind_name.name),
1877                sub_pattern: None,
1878            },
1879            Some(sub) => {
1880                let sub_rir = self.gen_match_arm_pattern(sub, nested);
1881                RirPatternBinding {
1882                    is_wildcard: false,
1883                    is_mut: false,
1884                    name: None,
1885                    sub_pattern: Some(Box::new(sub_rir)),
1886                }
1887            }
1888        }
1889    }
1890
1891    fn gen_block(&mut self, block: &gruel_parser::BlockExpr) -> InstRef {
1892        if block.statements.is_empty() {
1893            // No statements, just the final expression
1894            self.gen_expr(&block.expr)
1895        } else {
1896            // Collect all instruction refs for the block
1897            // statements + 1 for the final expression
1898            let mut inst_refs = Vec::with_capacity(block.statements.len() + 1);
1899
1900            // Generate all statements first. A single statement can produce
1901            // multiple RIR top-level instructions (e.g., a nested let
1902            // destructure elaborated into a tree of flat StructDestructures —
1903            // ADR-0049 Phase 4). Each produced InstRef becomes a block
1904            // statement; sema's block scope sees them in sequence so
1905            // intermediate bindings remain visible.
1906            for stmt in &block.statements {
1907                self.gen_statement_into(stmt, &mut inst_refs);
1908            }
1909
1910            // Generate the final expression
1911            let final_expr = self.gen_expr(&block.expr);
1912            inst_refs.push(final_expr.as_u32());
1913
1914            // Store the refs in extra data
1915            let extra_start = self.rir.add_extra(&inst_refs);
1916            let len = inst_refs.len() as u32;
1917
1918            self.rir.add_inst(Inst {
1919                data: InstData::Block { extra_start, len },
1920                span: block.span,
1921            })
1922        }
1923    }
1924
1925    /// Lower a nested let-destructure pattern into a tree of flat
1926    /// `StructDestructure` instructions plus intermediate synthetic bindings.
1927    ///
1928    /// For each field whose sub-pattern is itself a destructure, we emit a
1929    /// fresh `__nested_pat_N` binding for the outer level and follow with a
1930    /// child `emit_let_destructure` that consumes it via `VarRef`. Ownership
1931    /// transfers through the tree: the outer init is consumed by the outer
1932    /// destructure, each synthetic intermediate is consumed by the child
1933    /// destructure.
1934    ///
1935    /// Emits all instructions into `out`; returns nothing. The caller is
1936    /// responsible for wrapping a multi-instruction result in a Block if this
1937    /// let produces more than one RIR instruction.
1938    fn emit_let_destructure_into(
1939        &mut self,
1940        pattern: &Pattern,
1941        init: InstRef,
1942        span: gruel_util::Span,
1943        out: &mut Vec<InstRef>,
1944    ) {
1945        // Build the flat destructure fields plus a side-list of child sub-patterns
1946        // that need their own destructure pass.
1947        let (type_name, rir_fields, child_destructures) = match pattern {
1948            Pattern::Struct {
1949                type_name, fields, ..
1950            } => {
1951                let mut rir_fields = Vec::with_capacity(fields.len());
1952                let mut children: Vec<(Spur, &Pattern)> = Vec::new();
1953                for fp in fields {
1954                    // `..` rest pattern in a struct destructure: emit a
1955                    // marker field whose field_name is the sentinel `..` so
1956                    // sema recognizes this and relaxes the "all fields
1957                    // required" rule (ADR-0049 Phase 6).
1958                    let Some(field_name) = fp.field_name.as_ref() else {
1959                        rir_fields.push(RirDestructureField {
1960                            field_name: self.interner.get_or_intern_static(".."),
1961                            binding_name: None,
1962                            is_wildcard: true,
1963                            is_mut: false,
1964                        });
1965                        continue;
1966                    };
1967                    match &fp.sub {
1968                        None => rir_fields.push(RirDestructureField {
1969                            field_name: field_name.name,
1970                            binding_name: Some(field_name.name),
1971                            is_wildcard: false,
1972                            is_mut: fp.is_mut,
1973                        }),
1974                        Some(Pattern::Wildcard(_)) => rir_fields.push(RirDestructureField {
1975                            field_name: field_name.name,
1976                            binding_name: None,
1977                            is_wildcard: true,
1978                            is_mut: false,
1979                        }),
1980                        Some(Pattern::Ident {
1981                            is_mut,
1982                            name: bind_name,
1983                            ..
1984                        }) => rir_fields.push(RirDestructureField {
1985                            field_name: field_name.name,
1986                            binding_name: Some(bind_name.name),
1987                            is_wildcard: false,
1988                            is_mut: fp.is_mut || *is_mut,
1989                        }),
1990                        Some(sub @ (Pattern::Struct { .. } | Pattern::Tuple { .. })) => {
1991                            let fresh = self.fresh_nested_pat_name();
1992                            rir_fields.push(RirDestructureField {
1993                                field_name: field_name.name,
1994                                binding_name: Some(fresh),
1995                                is_wildcard: false,
1996                                is_mut: false,
1997                            });
1998                            children.push((fresh, sub));
1999                        }
2000                        Some(other) => panic!(
2001                            "unexpected sub-pattern in let struct destructure: {:?}",
2002                            other
2003                        ),
2004                    }
2005                }
2006                (type_name.name, rir_fields, children)
2007            }
2008            Pattern::Tuple { elems, .. } => {
2009                // Tuple destructuring with `..` at any position: elements
2010                // before the `..` get their literal positional index
2011                // (`"0"`, `"1"`, …); elements after the `..` get an
2012                // `..end_N` marker where N is the 0-indexed distance from
2013                // the end (so `(a, .., b, c)` emits `..end_1` for `b` and
2014                // `..end_0` for `c`). Sema resolves the end-marker against
2015                // the inferred tuple arity (ADR-0049 Phase 6).
2016                let rest_pos = elems
2017                    .iter()
2018                    .position(|e| matches!(e, TupleElemPattern::Rest(_)));
2019                let mut rir_fields = Vec::with_capacity(elems.len());
2020                let mut children: Vec<(Spur, &Pattern)> = Vec::new();
2021                for (i, elem) in elems.iter().enumerate() {
2022                    if let TupleElemPattern::Rest(_) = elem {
2023                        rir_fields.push(RirDestructureField {
2024                            field_name: self.interner.get_or_intern_static(".."),
2025                            binding_name: None,
2026                            is_wildcard: true,
2027                            is_mut: false,
2028                        });
2029                        continue;
2030                    }
2031                    let field_name = match rest_pos {
2032                        Some(rp) if i > rp => {
2033                            // Suffix position: encode as distance from end.
2034                            let from_end = elems.len() - 1 - i;
2035                            self.interner.get_or_intern(format!("..end_{}", from_end))
2036                        }
2037                        _ => self.interner.get_or_intern(i.to_string()),
2038                    };
2039                    match elem {
2040                        TupleElemPattern::Pattern(Pattern::Wildcard(_)) => {
2041                            rir_fields.push(RirDestructureField {
2042                                field_name,
2043                                binding_name: None,
2044                                is_wildcard: true,
2045                                is_mut: false,
2046                            });
2047                        }
2048                        TupleElemPattern::Pattern(Pattern::Ident { is_mut, name, .. }) => {
2049                            rir_fields.push(RirDestructureField {
2050                                field_name,
2051                                binding_name: Some(name.name),
2052                                is_wildcard: false,
2053                                is_mut: *is_mut,
2054                            });
2055                        }
2056                        TupleElemPattern::Pattern(
2057                            sub @ (Pattern::Struct { .. } | Pattern::Tuple { .. }),
2058                        ) => {
2059                            let fresh = self.fresh_nested_pat_name();
2060                            rir_fields.push(RirDestructureField {
2061                                field_name,
2062                                binding_name: Some(fresh),
2063                                is_wildcard: false,
2064                                is_mut: false,
2065                            });
2066                            children.push((fresh, sub));
2067                        }
2068                        TupleElemPattern::Pattern(other) => panic!(
2069                            "unexpected sub-pattern in let tuple destructure: {:?}",
2070                            other
2071                        ),
2072                        TupleElemPattern::Rest(_) => unreachable!("handled above"),
2073                    }
2074                }
2075                let tuple_type_name = self.interner.get_or_intern_static("__tuple__");
2076                (tuple_type_name, rir_fields, children)
2077            }
2078            other => panic!(
2079                "emit_let_destructure called on non-destructure pattern: {:?}",
2080                other
2081            ),
2082        };
2083
2084        let (fields_start, fields_len) = self.rir.add_destructure_fields(&rir_fields);
2085        let outer_inst = self.rir.add_inst(Inst {
2086            data: InstData::StructDestructure {
2087                type_name,
2088                fields_start,
2089                fields_len,
2090                init,
2091            },
2092            span,
2093        });
2094        out.push(outer_inst);
2095
2096        // Emit child destructures recursively.
2097        for (binding_name, sub) in child_destructures {
2098            let var_ref = self.rir.add_inst(Inst {
2099                data: InstData::VarRef { name: binding_name },
2100                span: sub.span(),
2101            });
2102            self.emit_let_destructure_into(sub, var_ref, sub.span(), out);
2103        }
2104    }
2105
2106    /// Generate RIR for a statement, appending produced top-level instruction
2107    /// refs to `out`. A single AST statement can produce multiple RIR
2108    /// instructions when lowering nested destructures (ADR-0049 Phase 4).
2109    fn gen_statement_into(&mut self, stmt: &Statement, out: &mut Vec<u32>) {
2110        if let Statement::Let(let_stmt) = stmt
2111            && matches!(
2112                &let_stmt.pattern,
2113                Pattern::Struct { .. } | Pattern::Tuple { .. }
2114            )
2115        {
2116            let init = self.gen_expr(&let_stmt.init);
2117            let mut emitted = Vec::new();
2118            self.emit_let_destructure_into(&let_stmt.pattern, init, let_stmt.span, &mut emitted);
2119            for r in emitted {
2120                out.push(r.as_u32());
2121            }
2122            return;
2123        }
2124        let single = self.gen_statement(stmt);
2125        out.push(single.as_u32());
2126    }
2127
2128    fn gen_statement(&mut self, stmt: &Statement) -> InstRef {
2129        match stmt {
2130            Statement::Let(let_stmt) => match &let_stmt.pattern {
2131                // Struct / Tuple destructure patterns. Both lower to
2132                // `InstData::StructDestructure`; tuple patterns use the
2133                // `__tuple__` sentinel type name (ADR-0048).
2134                //
2135                // Nested sub-patterns (ADR-0049) are elaborated in astgen: each
2136                // nested sub-pattern position gets a synthetic intermediate
2137                // binding, and a child StructDestructure is emitted against it.
2138                // This keeps RIR / sema / CFG flat and reuses existing
2139                // infrastructure unchanged.
2140                Pattern::Struct { .. } | Pattern::Tuple { .. } => {
2141                    // Destructure lets are special: they can produce multiple
2142                    // top-level RIR instructions for nested patterns. Callers
2143                    // that can fan out (gen_block via gen_statement_into)
2144                    // handle this. Callers that can't fan out (nothing
2145                    // currently) would see only the outer instruction.
2146                    let init = self.gen_expr(&let_stmt.init);
2147                    let mut emitted = Vec::new();
2148                    self.emit_let_destructure_into(
2149                        &let_stmt.pattern,
2150                        init,
2151                        let_stmt.span,
2152                        &mut emitted,
2153                    );
2154                    // Safe: emit_let_destructure_into always pushes at least one.
2155                    *emitted
2156                        .first()
2157                        .expect("destructure must emit at least one instruction")
2158                }
2159                pattern => {
2160                    let directives = self.convert_directives(&let_stmt.directives);
2161                    let (directives_start, directives_len) = self.rir.add_directives(&directives);
2162                    let is_mut = match pattern {
2163                        Pattern::Ident { is_mut, .. } => *is_mut || let_stmt.is_mut,
2164                        _ => let_stmt.is_mut,
2165                    };
2166                    let name = match pattern {
2167                        Pattern::Ident { name, .. } => Some(name.name),
2168                        Pattern::Wildcard(_) => None,
2169                        _ => unreachable!(
2170                            "Phase 1 let-statement parser only emits Ident/Wildcard/Struct/Tuple; got {:?}",
2171                            pattern
2172                        ),
2173                    };
2174                    let ty = let_stmt.ty.as_ref().map(|t| self.intern_type(t));
2175                    let init = self.gen_expr(&let_stmt.init);
2176                    self.rir.add_inst(Inst {
2177                        data: InstData::Alloc {
2178                            directives_start,
2179                            directives_len,
2180                            name,
2181                            is_mut,
2182                            ty,
2183                            init,
2184                        },
2185                        span: let_stmt.span,
2186                    })
2187                }
2188            },
2189            Statement::Assign(assign) => {
2190                let value = self.gen_expr(&assign.value);
2191                match &assign.target {
2192                    AssignTarget::Var(ident) => {
2193                        self.rir.add_inst(Inst {
2194                            data: InstData::Assign {
2195                                name: ident.name, // Already a Spur
2196                                value,
2197                            },
2198                            span: assign.span,
2199                        })
2200                    }
2201                    AssignTarget::Field(field_expr) => {
2202                        let base = self.gen_expr(&field_expr.base);
2203                        self.rir.add_inst(Inst {
2204                            data: InstData::FieldSet {
2205                                base,
2206                                field: field_expr.field.name, // Already a Spur
2207                                value,
2208                            },
2209                            span: assign.span,
2210                        })
2211                    }
2212                    AssignTarget::Index(index_expr) => {
2213                        let base = self.gen_expr(&index_expr.base);
2214                        let index = self.gen_expr(&index_expr.index);
2215                        self.rir.add_inst(Inst {
2216                            data: InstData::IndexSet { base, index, value },
2217                            span: assign.span,
2218                        })
2219                    }
2220                }
2221            }
2222            Statement::Expr(expr) => {
2223                // Expression statements are evaluated for side effects
2224                // The result is discarded, but we still return the InstRef
2225                self.gen_expr(expr)
2226            }
2227        }
2228    }
2229}
2230
2231/// Whether a pattern is irrefutable (matches every value of its type).
2232///
2233/// Used by `try_elaborate_irrefutable_match` to decide when a tuple / struct
2234/// match arm can be lowered as a straight let-destructure. Literals and
2235/// variant patterns are always refutable; wildcard, ident, and a tuple /
2236/// struct whose every leaf is irrefutable are not.
2237fn is_irrefutable_destructure(pat: &Pattern) -> bool {
2238    match pat {
2239        Pattern::Wildcard(_) | Pattern::Ident { .. } => true,
2240        Pattern::Int(_) | Pattern::NegInt(_) | Pattern::Bool(_) => false,
2241        Pattern::Path(_) => false,
2242        Pattern::DataVariant { .. } | Pattern::StructVariant { .. } => false,
2243        Pattern::Struct { fields, .. } => fields.iter().all(|f| match &f.sub {
2244            None => true,
2245            Some(sub) => is_irrefutable_destructure(sub),
2246        }),
2247        Pattern::Tuple { elems, .. } => elems.iter().all(|e| match e {
2248            TupleElemPattern::Pattern(p) => is_irrefutable_destructure(p),
2249            TupleElemPattern::Rest(_) => true,
2250        }),
2251        // ADR-0079 Phase 3: an unroll-arm template stands in for
2252        // refutable variant patterns once expanded.
2253        Pattern::ComptimeUnrollArm { .. } => false,
2254    }
2255}
2256
2257// Note: the flat-lowering helpers `field_pattern_to_rir_destructure_field`,
2258// `tuple_elem_to_rir_destructure_field`, `tuple_elem_to_rir_binding`, and
2259// `field_pattern_to_rir_binding` were superseded by
2260// `AstGen::emit_let_destructure` (Phase 4) and
2261// `AstGen::tuple_elem_to_rir_binding_or_capture` /
2262// `AstGen::field_pattern_to_rir_binding_or_capture` (Phase 4b), which handle
2263// nested destructure and variant sub-pattern shapes uniformly by emitting
2264// trees of StructDestructure instructions or capturing nested sub-patterns for
2265// arm-body elaboration.
2266
2267#[cfg(test)]
2268mod tests {
2269    use super::*;
2270    use crate::print::RirPrinter;
2271    use gruel_lexer::Lexer;
2272    use gruel_parser::Parser;
2273
2274    fn gen_rir(source: &str) -> (Rir, ThreadedRodeo) {
2275        let lexer = Lexer::new(source);
2276        let (tokens, interner) = lexer.tokenize().unwrap();
2277        let parser = Parser::new(tokens, interner);
2278        let (ast, interner) = parser.parse().unwrap();
2279
2280        let astgen = AstGen::new(&ast, &interner);
2281        let rir = astgen.generate();
2282        (rir, interner)
2283    }
2284
2285    #[test]
2286    fn test_gen_simple_function() {
2287        let (rir, interner) = gen_rir("fn main() -> i32 { 42 }");
2288
2289        // Should have 2 instructions: IntConst(42), FnDecl
2290        assert_eq!(rir.len(), 2);
2291
2292        // Check the function declaration
2293        let (_, fn_inst) = rir.iter().last().unwrap();
2294        match &fn_inst.data {
2295            InstData::FnDecl {
2296                name,
2297                params_start,
2298                params_len,
2299                return_type,
2300                body,
2301                has_self,
2302                ..
2303            } => {
2304                assert_eq!(interner.resolve(name), "main");
2305                let params = rir.get_params(*params_start, *params_len);
2306                assert!(params.is_empty());
2307                assert_eq!(interner.resolve(return_type), "i32");
2308                assert!(!has_self); // Regular functions don't have self
2309                // Body should be the int constant
2310                let body_inst = rir.get(*body);
2311                assert!(matches!(body_inst.data, InstData::IntConst(42)));
2312            }
2313            _ => panic!("expected FnDecl"),
2314        }
2315    }
2316
2317    #[test]
2318    fn test_gen_addition() {
2319        let (rir, _) = gen_rir("fn main() -> i32 { 1 + 2 }");
2320
2321        // Should have: IntConst(1), IntConst(2), Add, FnDecl
2322        assert_eq!(rir.len(), 4);
2323
2324        // Check add instruction
2325        let add_inst = rir.get(InstRef::from_raw(2));
2326        match &add_inst.data {
2327            InstData::Bin {
2328                op: BinOp::Add,
2329                lhs,
2330                rhs,
2331            } => {
2332                assert!(matches!(rir.get(*lhs).data, InstData::IntConst(1)));
2333                assert!(matches!(rir.get(*rhs).data, InstData::IntConst(2)));
2334            }
2335            _ => panic!("expected Add"),
2336        }
2337    }
2338
2339    #[test]
2340    fn test_gen_precedence() {
2341        let (rir, _) = gen_rir("fn main() -> i32 { 1 + 2 * 3 }");
2342
2343        // Should have: IntConst(1), IntConst(2), IntConst(3), Mul, Add, FnDecl
2344        assert_eq!(rir.len(), 6);
2345
2346        // Check that add is the body (mul is nested)
2347        let fn_inst = rir.iter().last().unwrap().1;
2348        match &fn_inst.data {
2349            InstData::FnDecl { body, .. } => {
2350                let body_inst = rir.get(*body);
2351                match &body_inst.data {
2352                    InstData::Bin {
2353                        op: BinOp::Add,
2354                        lhs,
2355                        rhs,
2356                    } => {
2357                        // lhs should be IntConst(1)
2358                        assert!(matches!(rir.get(*lhs).data, InstData::IntConst(1)));
2359                        // rhs should be Mul
2360                        assert!(matches!(
2361                            rir.get(*rhs).data,
2362                            InstData::Bin { op: BinOp::Mul, .. }
2363                        ));
2364                    }
2365                    _ => panic!("expected Add"),
2366                }
2367            }
2368            _ => panic!("expected FnDecl"),
2369        }
2370    }
2371
2372    #[test]
2373    fn test_gen_negation() {
2374        let (rir, _) = gen_rir("fn main() -> i32 { -42 }");
2375
2376        // Should have: IntConst(42), Neg, FnDecl
2377        assert_eq!(rir.len(), 3);
2378
2379        // Check neg instruction
2380        let neg_inst = rir.get(InstRef::from_raw(1));
2381        match &neg_inst.data {
2382            InstData::Unary {
2383                op: UnaryOp::Neg,
2384                operand,
2385            } => {
2386                assert!(matches!(rir.get(*operand).data, InstData::IntConst(42)));
2387            }
2388            _ => panic!("expected Neg"),
2389        }
2390    }
2391
2392    #[test]
2393    fn test_gen_parens() {
2394        let (rir, _) = gen_rir("fn main() -> i32 { (1 + 2) * 3 }");
2395
2396        // Should have: IntConst(1), IntConst(2), Add, IntConst(3), Mul, FnDecl
2397        // Parens don't generate instructions, they just affect evaluation order
2398        assert_eq!(rir.len(), 6);
2399
2400        // Check that mul is the body (add is nested)
2401        let fn_inst = rir.iter().last().unwrap().1;
2402        match &fn_inst.data {
2403            InstData::FnDecl { body, .. } => {
2404                let body_inst = rir.get(*body);
2405                match &body_inst.data {
2406                    InstData::Bin {
2407                        op: BinOp::Mul,
2408                        lhs,
2409                        rhs,
2410                    } => {
2411                        // lhs should be Add
2412                        assert!(matches!(
2413                            rir.get(*lhs).data,
2414                            InstData::Bin { op: BinOp::Add, .. }
2415                        ));
2416                        // rhs should be IntConst(3)
2417                        assert!(matches!(rir.get(*rhs).data, InstData::IntConst(3)));
2418                    }
2419                    _ => panic!("expected Mul"),
2420                }
2421            }
2422            _ => panic!("expected FnDecl"),
2423        }
2424    }
2425
2426    #[test]
2427    fn test_gen_all_binary_ops() {
2428        for (src, expected) in [
2429            ("fn main() -> i32 { 1 + 2 }", BinOp::Add),
2430            ("fn main() -> i32 { 1 - 2 }", BinOp::Sub),
2431            ("fn main() -> i32 { 1 * 2 }", BinOp::Mul),
2432            ("fn main() -> i32 { 1 / 2 }", BinOp::Div),
2433            ("fn main() -> i32 { 1 % 2 }", BinOp::Mod),
2434        ] {
2435            let (rir, _) = gen_rir(src);
2436            match rir.get(InstRef::from_raw(2)).data {
2437                InstData::Bin { op, .. } => assert_eq!(op, expected),
2438                _ => panic!("expected Bin for {}", src),
2439            }
2440        }
2441    }
2442
2443    #[test]
2444    fn test_gen_let_binding() {
2445        let (rir, interner) = gen_rir("fn main() -> i32 { let x = 42; x }");
2446
2447        // Find the Alloc instruction
2448        let alloc_inst = rir
2449            .iter()
2450            .find(|(_, inst)| matches!(inst.data, InstData::Alloc { .. }));
2451        assert!(alloc_inst.is_some());
2452
2453        let (_, inst) = alloc_inst.unwrap();
2454        match &inst.data {
2455            InstData::Alloc {
2456                name,
2457                is_mut,
2458                ty,
2459                init,
2460                ..
2461            } => {
2462                assert_eq!(interner.resolve(&name.unwrap()), "x");
2463                assert!(!is_mut);
2464                assert!(ty.is_none());
2465                assert!(matches!(rir.get(*init).data, InstData::IntConst(42)));
2466            }
2467            _ => panic!("expected Alloc"),
2468        }
2469    }
2470
2471    #[test]
2472    fn test_gen_let_mut() {
2473        let (rir, interner) = gen_rir("fn main() -> i32 { let mut x = 10; x }");
2474
2475        let alloc_inst = rir
2476            .iter()
2477            .find(|(_, inst)| matches!(inst.data, InstData::Alloc { .. }));
2478        assert!(alloc_inst.is_some());
2479
2480        let (_, inst) = alloc_inst.unwrap();
2481        match &inst.data {
2482            InstData::Alloc { name, is_mut, .. } => {
2483                assert_eq!(interner.resolve(&name.unwrap()), "x");
2484                assert!(*is_mut);
2485            }
2486            _ => panic!("expected Alloc"),
2487        }
2488    }
2489
2490    #[test]
2491    fn test_gen_var_ref() {
2492        let (rir, interner) = gen_rir("fn main() -> i32 { let x = 42; x }");
2493
2494        // The body should be a Block (since there are statements)
2495        let fn_inst = rir.iter().last().unwrap().1;
2496        match &fn_inst.data {
2497            InstData::FnDecl { body, .. } => {
2498                let body_inst = rir.get(*body);
2499                match &body_inst.data {
2500                    InstData::Block { extra_start, len } => {
2501                        // Block contains: Alloc, VarRef
2502                        assert_eq!(*len, 2);
2503                        let inst_refs = rir.get_extra(*extra_start, *len);
2504                        // Last instruction in block is the VarRef
2505                        let var_ref_inst = rir.get(InstRef::from_raw(inst_refs[1]));
2506                        match &var_ref_inst.data {
2507                            InstData::VarRef { name } => {
2508                                assert_eq!(interner.resolve(name), "x");
2509                            }
2510                            _ => panic!("expected VarRef"),
2511                        }
2512                    }
2513                    _ => panic!("expected Block, got {:?}", body_inst.data),
2514                }
2515            }
2516            _ => panic!("expected FnDecl"),
2517        }
2518    }
2519
2520    #[test]
2521    fn test_gen_assignment() {
2522        let (rir, interner) = gen_rir("fn main() -> i32 { let mut x = 10; x = 20; x }");
2523
2524        // Find the Assign instruction
2525        let assign_inst = rir
2526            .iter()
2527            .find(|(_, inst)| matches!(inst.data, InstData::Assign { .. }));
2528        assert!(assign_inst.is_some());
2529
2530        let (_, inst) = assign_inst.unwrap();
2531        match &inst.data {
2532            InstData::Assign { name, value } => {
2533                assert_eq!(interner.resolve(name), "x");
2534                assert!(matches!(rir.get(*value).data, InstData::IntConst(20)));
2535            }
2536            _ => panic!("expected Assign"),
2537        }
2538    }
2539
2540    #[test]
2541    fn test_gen_multiple_statements() {
2542        let (rir, _interner) = gen_rir("fn main() -> i32 { let x = 1; let y = 2; x + y }");
2543
2544        // Count Alloc instructions
2545        let alloc_count = rir
2546            .iter()
2547            .filter(|(_, inst)| matches!(inst.data, InstData::Alloc { .. }))
2548            .count();
2549        assert_eq!(alloc_count, 2);
2550
2551        // Check the body is a Block containing the allocs and the Add
2552        let fn_inst = rir.iter().last().unwrap().1;
2553        match &fn_inst.data {
2554            InstData::FnDecl { body, .. } => {
2555                let body_inst = rir.get(*body);
2556                match &body_inst.data {
2557                    InstData::Block { extra_start, len } => {
2558                        // Block contains: Alloc(x), Alloc(y), Add
2559                        assert_eq!(*len, 3);
2560                        let inst_refs = rir.get_extra(*extra_start, *len);
2561                        // Last instruction in block is the Add
2562                        let add_inst = rir.get(InstRef::from_raw(inst_refs[2]));
2563                        assert!(matches!(
2564                            add_inst.data,
2565                            InstData::Bin { op: BinOp::Add, .. }
2566                        ));
2567                    }
2568                    _ => panic!("expected Block"),
2569                }
2570            }
2571            _ => panic!("expected FnDecl"),
2572        }
2573    }
2574
2575    // Struct with methods tests
2576    #[test]
2577    fn test_gen_struct_with_method() {
2578        let source = r#"
2579            struct Point {
2580                x: i32,
2581                y: i32,
2582                fn get_x(self) -> i32 {
2583                    self.x
2584                }
2585            }
2586            fn main() -> i32 { 0 }
2587        "#;
2588        let (rir, interner) = gen_rir(source);
2589
2590        // Find the StructDecl instruction
2591        let struct_decl = rir
2592            .iter()
2593            .find(|(_, inst)| matches!(inst.data, InstData::StructDecl { .. }));
2594        assert!(struct_decl.is_some(), "Expected StructDecl instruction");
2595
2596        let (_, inst) = struct_decl.unwrap();
2597        match &inst.data {
2598            InstData::StructDecl {
2599                name,
2600                methods_start,
2601                methods_len,
2602                ..
2603            } => {
2604                assert_eq!(interner.resolve(name), "Point");
2605                let methods = rir.get_inst_refs(*methods_start, *methods_len);
2606                assert_eq!(methods.len(), 1);
2607
2608                // Check the method is a FnDecl with has_self=true
2609                let method_inst = rir.get(methods[0]);
2610                match &method_inst.data {
2611                    InstData::FnDecl { name, has_self, .. } => {
2612                        assert_eq!(interner.resolve(name), "get_x");
2613                        assert!(*has_self);
2614                    }
2615                    _ => panic!("expected FnDecl"),
2616                }
2617            }
2618            _ => panic!("expected StructDecl"),
2619        }
2620    }
2621
2622    #[test]
2623    fn test_gen_struct_with_multiple_methods() {
2624        let source = r#"
2625            struct Point {
2626                x: i32,
2627                y: i32,
2628                fn get_x(self) -> i32 { self.x }
2629                fn get_y(self) -> i32 { self.y }
2630                fn origin() -> Point { Point { x: 0, y: 0 } }
2631            }
2632            fn main() -> i32 { 0 }
2633        "#;
2634        let (rir, interner) = gen_rir(source);
2635
2636        let struct_decl = rir
2637            .iter()
2638            .find(|(_, inst)| matches!(inst.data, InstData::StructDecl { .. }));
2639        assert!(struct_decl.is_some());
2640
2641        let (_, inst) = struct_decl.unwrap();
2642        match &inst.data {
2643            InstData::StructDecl {
2644                methods_start,
2645                methods_len,
2646                ..
2647            } => {
2648                let methods = rir.get_inst_refs(*methods_start, *methods_len);
2649                assert_eq!(methods.len(), 3);
2650
2651                // Check get_x and get_y have self, origin does not
2652                for method_ref in methods {
2653                    let method_inst = rir.get(method_ref);
2654                    match &method_inst.data {
2655                        InstData::FnDecl { name, has_self, .. } => {
2656                            let method_name = interner.resolve(name);
2657                            if method_name == "origin" {
2658                                assert!(!has_self, "origin should not have self");
2659                            } else {
2660                                assert!(*has_self, "{} should have self", method_name);
2661                            }
2662                        }
2663                        _ => panic!("expected FnDecl"),
2664                    }
2665                }
2666            }
2667            _ => panic!("expected StructDecl"),
2668        }
2669    }
2670
2671    #[test]
2672    fn test_gen_method_call() {
2673        let source = r#"
2674            struct Point {
2675                x: i32,
2676                fn get_x(self) -> i32 { self.x }
2677            }
2678            fn main() -> i32 {
2679                let p = Point { x: 42 };
2680                p.get_x()
2681            }
2682        "#;
2683        let (rir, interner) = gen_rir(source);
2684
2685        // Find the MethodCall instruction
2686        let method_call = rir
2687            .iter()
2688            .find(|(_, inst)| matches!(inst.data, InstData::MethodCall { .. }));
2689        assert!(method_call.is_some(), "Expected MethodCall instruction");
2690
2691        let (_, inst) = method_call.unwrap();
2692        match &inst.data {
2693            InstData::MethodCall {
2694                receiver: _,
2695                method,
2696                args_start,
2697                args_len,
2698            } => {
2699                assert_eq!(interner.resolve(method), "get_x");
2700                let args = rir.get_call_args(*args_start, *args_len);
2701                assert!(args.is_empty()); // No explicit args (self is implicit)
2702            }
2703            _ => panic!("expected MethodCall"),
2704        }
2705    }
2706
2707    #[test]
2708    fn test_gen_assoc_fn_call() {
2709        let source = r#"
2710            struct Point {
2711                x: i32,
2712                y: i32,
2713                fn origin() -> Point { Point { x: 0, y: 0 } }
2714            }
2715            fn main() -> i32 {
2716                let p = Point::origin();
2717                0
2718            }
2719        "#;
2720        let (rir, interner) = gen_rir(source);
2721
2722        // Find the AssocFnCall instruction
2723        let assoc_fn_call = rir
2724            .iter()
2725            .find(|(_, inst)| matches!(inst.data, InstData::AssocFnCall { .. }));
2726        assert!(assoc_fn_call.is_some(), "Expected AssocFnCall instruction");
2727
2728        let (_, inst) = assoc_fn_call.unwrap();
2729        match &inst.data {
2730            InstData::AssocFnCall {
2731                type_name,
2732                function,
2733                args_start,
2734                args_len,
2735            } => {
2736                assert_eq!(interner.resolve(type_name), "Point");
2737                assert_eq!(interner.resolve(function), "origin");
2738                let args = rir.get_call_args(*args_start, *args_len);
2739                assert!(args.is_empty());
2740            }
2741            _ => panic!("expected AssocFnCall"),
2742        }
2743    }
2744
2745    // Pattern tests
2746    #[test]
2747    fn test_gen_match_wildcard_pattern() {
2748        let source = r#"
2749            fn main() -> i32 {
2750                let x = 5;
2751                match x {
2752                    _ => 42,
2753                }
2754            }
2755        "#;
2756        let (rir, _interner) = gen_rir(source);
2757
2758        // Find the Match instruction
2759        let match_inst = rir
2760            .iter()
2761            .find(|(_, inst)| matches!(inst.data, InstData::Match { .. }));
2762        assert!(match_inst.is_some(), "Expected Match instruction");
2763
2764        let (_, inst) = match_inst.unwrap();
2765        match &inst.data {
2766            InstData::Match {
2767                arms_start,
2768                arms_len,
2769                ..
2770            } => {
2771                let arms = rir.get_match_arms(*arms_start, *arms_len);
2772                assert_eq!(arms.len(), 1);
2773                assert!(matches!(arms[0].0, RirPattern::Wildcard(_)));
2774            }
2775            _ => panic!("expected Match"),
2776        }
2777    }
2778
2779    #[test]
2780    fn test_gen_match_int_patterns() {
2781        let source = r#"
2782            fn main() -> i32 {
2783                let x = 5;
2784                match x {
2785                    1 => 10,
2786                    2 => 20,
2787                    _ => 0,
2788                }
2789            }
2790        "#;
2791        let (rir, _interner) = gen_rir(source);
2792
2793        let match_inst = rir
2794            .iter()
2795            .find(|(_, inst)| matches!(inst.data, InstData::Match { .. }));
2796        assert!(match_inst.is_some());
2797
2798        let (_, inst) = match_inst.unwrap();
2799        match &inst.data {
2800            InstData::Match {
2801                arms_start,
2802                arms_len,
2803                ..
2804            } => {
2805                let arms = rir.get_match_arms(*arms_start, *arms_len);
2806                assert_eq!(arms.len(), 3);
2807                assert!(matches!(arms[0].0, RirPattern::Int(1, _)));
2808                assert!(matches!(arms[1].0, RirPattern::Int(2, _)));
2809                assert!(matches!(arms[2].0, RirPattern::Wildcard(_)));
2810            }
2811            _ => panic!("expected Match"),
2812        }
2813    }
2814
2815    #[test]
2816    fn test_gen_match_negative_int_pattern() {
2817        let source = r#"
2818            fn main() -> i32 {
2819                let x: i32 = -5;
2820                match x {
2821                    -5 => 1,
2822                    -10 => 2,
2823                    _ => 0,
2824                }
2825            }
2826        "#;
2827        let (rir, _interner) = gen_rir(source);
2828
2829        let match_inst = rir
2830            .iter()
2831            .find(|(_, inst)| matches!(inst.data, InstData::Match { .. }));
2832        assert!(match_inst.is_some());
2833
2834        let (_, inst) = match_inst.unwrap();
2835        match &inst.data {
2836            InstData::Match {
2837                arms_start,
2838                arms_len,
2839                ..
2840            } => {
2841                let arms = rir.get_match_arms(*arms_start, *arms_len);
2842                assert_eq!(arms.len(), 3);
2843                assert!(matches!(arms[0].0, RirPattern::Int(-5, _)));
2844                assert!(matches!(arms[1].0, RirPattern::Int(-10, _)));
2845                assert!(matches!(arms[2].0, RirPattern::Wildcard(_)));
2846            }
2847            _ => panic!("expected Match"),
2848        }
2849    }
2850
2851    #[test]
2852    fn test_gen_match_bool_patterns() {
2853        let source = r#"
2854            fn main() -> i32 {
2855                let b = true;
2856                match b {
2857                    true => 1,
2858                    false => 0,
2859                }
2860            }
2861        "#;
2862        let (rir, _interner) = gen_rir(source);
2863
2864        let match_inst = rir
2865            .iter()
2866            .find(|(_, inst)| matches!(inst.data, InstData::Match { .. }));
2867        assert!(match_inst.is_some());
2868
2869        let (_, inst) = match_inst.unwrap();
2870        match &inst.data {
2871            InstData::Match {
2872                arms_start,
2873                arms_len,
2874                ..
2875            } => {
2876                let arms = rir.get_match_arms(*arms_start, *arms_len);
2877                assert_eq!(arms.len(), 2);
2878                assert!(matches!(arms[0].0, RirPattern::Bool(true, _)));
2879                assert!(matches!(arms[1].0, RirPattern::Bool(false, _)));
2880            }
2881            _ => panic!("expected Match"),
2882        }
2883    }
2884
2885    #[test]
2886    fn test_gen_match_enum_patterns() {
2887        let source = r#"
2888            enum Color { Red, Green, Blue }
2889            fn main() -> i32 {
2890                let c = Color::Red;
2891                match c {
2892                    Color::Red => 1,
2893                    Color::Green => 2,
2894                    Color::Blue => 3,
2895                }
2896            }
2897        "#;
2898        let (rir, interner) = gen_rir(source);
2899
2900        let match_inst = rir
2901            .iter()
2902            .find(|(_, inst)| matches!(inst.data, InstData::Match { .. }));
2903        assert!(match_inst.is_some());
2904
2905        let (_, inst) = match_inst.unwrap();
2906        match &inst.data {
2907            InstData::Match {
2908                arms_start,
2909                arms_len,
2910                ..
2911            } => {
2912                let arms = rir.get_match_arms(*arms_start, *arms_len);
2913                assert_eq!(arms.len(), 3);
2914
2915                // Check first arm is Color::Red
2916                match &arms[0].0 {
2917                    RirPattern::Path {
2918                        type_name, variant, ..
2919                    } => {
2920                        assert_eq!(interner.resolve(type_name), "Color");
2921                        assert_eq!(interner.resolve(variant), "Red");
2922                    }
2923                    _ => panic!("expected Path pattern"),
2924                }
2925
2926                // Check second arm is Color::Green
2927                match &arms[1].0 {
2928                    RirPattern::Path {
2929                        type_name, variant, ..
2930                    } => {
2931                        assert_eq!(interner.resolve(type_name), "Color");
2932                        assert_eq!(interner.resolve(variant), "Green");
2933                    }
2934                    _ => panic!("expected Path pattern"),
2935                }
2936
2937                // Check third arm is Color::Blue
2938                match &arms[2].0 {
2939                    RirPattern::Path {
2940                        type_name, variant, ..
2941                    } => {
2942                        assert_eq!(interner.resolve(type_name), "Color");
2943                        assert_eq!(interner.resolve(variant), "Blue");
2944                    }
2945                    _ => panic!("expected Path pattern"),
2946                }
2947            }
2948            _ => panic!("expected Match"),
2949        }
2950    }
2951
2952    #[test]
2953    fn test_gen_self_expr() {
2954        let source = r#"
2955            struct Point {
2956                x: i32,
2957                fn get_x(self) -> i32 { self.x }
2958            }
2959            fn main() -> i32 { 0 }
2960        "#;
2961        let (rir, interner) = gen_rir(source);
2962
2963        // Find the VarRef instruction for "self"
2964        let self_ref = rir.iter().find(|(_, inst)| match &inst.data {
2965            InstData::VarRef { name } => interner.resolve(name) == "self",
2966            _ => false,
2967        });
2968        assert!(self_ref.is_some(), "Expected self VarRef instruction");
2969    }
2970
2971    #[test]
2972    fn test_gen_enum_variant() {
2973        let source = r#"
2974            enum Color { Red, Green, Blue }
2975            fn main() -> i32 {
2976                let c = Color::Red;
2977                0
2978            }
2979        "#;
2980        let (rir, interner) = gen_rir(source);
2981
2982        // Find the EnumVariant instruction
2983        let enum_variant = rir
2984            .iter()
2985            .find(|(_, inst)| matches!(inst.data, InstData::EnumVariant { .. }));
2986        assert!(enum_variant.is_some(), "Expected EnumVariant instruction");
2987
2988        let (_, inst) = enum_variant.unwrap();
2989        match &inst.data {
2990            InstData::EnumVariant {
2991                type_name, variant, ..
2992            } => {
2993                assert_eq!(interner.resolve(type_name), "Color");
2994                assert_eq!(interner.resolve(variant), "Red");
2995            }
2996            _ => panic!("expected EnumVariant"),
2997        }
2998    }
2999
3000    #[test]
3001    fn test_gen_method_with_params() {
3002        let source = r#"
3003            struct Counter {
3004                value: i32,
3005                fn add(self, amount: i32) -> i32 { self.value + amount }
3006            }
3007            fn main() -> i32 { 0 }
3008        "#;
3009        let (rir, interner) = gen_rir(source);
3010
3011        // Find the struct declaration
3012        let struct_decl = rir
3013            .iter()
3014            .find(|(_, inst)| matches!(inst.data, InstData::StructDecl { .. }));
3015        assert!(struct_decl.is_some());
3016
3017        let (_, inst) = struct_decl.unwrap();
3018        match &inst.data {
3019            InstData::StructDecl {
3020                methods_start,
3021                methods_len,
3022                ..
3023            } => {
3024                let methods = rir.get_inst_refs(*methods_start, *methods_len);
3025                let method_inst = rir.get(methods[0]);
3026                match &method_inst.data {
3027                    InstData::FnDecl {
3028                        name,
3029                        params_start,
3030                        params_len,
3031                        has_self,
3032                        ..
3033                    } => {
3034                        assert_eq!(interner.resolve(name), "add");
3035                        assert!(*has_self);
3036                        // params should contain 'amount', not 'self'
3037                        let params = rir.get_params(*params_start, *params_len);
3038                        assert_eq!(params.len(), 1);
3039                        assert_eq!(interner.resolve(&params[0].name), "amount");
3040                    }
3041                    _ => panic!("expected FnDecl"),
3042                }
3043            }
3044            _ => panic!("expected StructDecl"),
3045        }
3046    }
3047
3048    // RirPrinter integration test with actual generated RIR
3049    #[test]
3050    fn test_printer_integration() {
3051        let source = r#"
3052            struct Point {
3053                x: i32,
3054                y: i32,
3055                fn origin() -> Point { Point { x: 0, y: 0 } }
3056            }
3057            fn main() -> i32 {
3058                let p = Point::origin();
3059                p.x
3060            }
3061        "#;
3062        let (rir, interner) = gen_rir(source);
3063
3064        let printer = RirPrinter::new(&rir, &interner);
3065        let output = printer.to_string();
3066
3067        // Check key elements are present in the output
3068        assert!(output.contains("struct Point"));
3069        assert!(output.contains("methods: ["));
3070        assert!(output.contains("fn origin"));
3071        assert!(output.contains("fn main"));
3072        assert!(output.contains("struct_init Point"));
3073        assert!(output.contains("assoc_fn_call Point::origin"));
3074        assert!(output.contains("field_get"));
3075    }
3076
3077    // ===== Function span tests =====
3078
3079    #[test]
3080    fn test_function_spans_simple() {
3081        let (rir, interner) = gen_rir("fn main() -> i32 { 42 }");
3082
3083        // Should have exactly one function span
3084        assert_eq!(rir.function_count(), 1);
3085
3086        let spans: Vec<_> = rir.functions().collect();
3087        assert_eq!(spans.len(), 1);
3088
3089        let span = &spans[0];
3090        assert_eq!(interner.resolve(&span.name), "main");
3091
3092        // The function should have 2 instructions: IntConst(42) and FnDecl
3093        assert_eq!(span.instruction_count(), 2);
3094
3095        // The FnDecl should be the last instruction
3096        let fn_inst = rir.get(span.decl);
3097        assert!(matches!(fn_inst.data, InstData::FnDecl { .. }));
3098    }
3099
3100    #[test]
3101    fn test_function_spans_multiple_functions() {
3102        let source = r#"
3103            fn helper() -> i32 { 1 }
3104            fn main() -> i32 { 42 }
3105        "#;
3106        let (rir, interner) = gen_rir(source);
3107
3108        // Should have two function spans
3109        assert_eq!(rir.function_count(), 2);
3110
3111        let spans: Vec<_> = rir.functions().collect();
3112        assert_eq!(spans.len(), 2);
3113
3114        // First function: helper
3115        assert_eq!(interner.resolve(&spans[0].name), "helper");
3116        assert_eq!(spans[0].instruction_count(), 2);
3117
3118        // Second function: main
3119        assert_eq!(interner.resolve(&spans[1].name), "main");
3120        assert_eq!(spans[1].instruction_count(), 2);
3121
3122        // Function spans should be non-overlapping
3123        assert!(
3124            spans[0].decl.as_u32() < spans[1].body_start.as_u32(),
3125            "helper should end before main starts"
3126        );
3127    }
3128
3129    #[test]
3130    fn test_function_spans_with_methods() {
3131        let source = r#"
3132            struct Point {
3133                x: i32,
3134                fn get_x(self) -> i32 { self.x }
3135                fn origin() -> Point { Point { x: 0 } }
3136            }
3137            fn main() -> i32 { 0 }
3138        "#;
3139        let (rir, interner) = gen_rir(source);
3140
3141        // Should have three function spans: get_x, origin, main
3142        assert_eq!(rir.function_count(), 3);
3143
3144        let spans: Vec<_> = rir.functions().collect();
3145
3146        // Methods should be tracked as well
3147        let names: Vec<_> = spans.iter().map(|s| interner.resolve(&s.name)).collect();
3148        assert!(names.contains(&"get_x"));
3149        assert!(names.contains(&"origin"));
3150        assert!(names.contains(&"main"));
3151    }
3152
3153    #[test]
3154    fn test_function_view() {
3155        let source = r#"
3156            fn helper(x: i32) -> i32 { x + 1 }
3157            fn main() -> i32 { helper(41) }
3158        "#;
3159        let (rir, interner) = gen_rir(source);
3160
3161        // Find the main function span
3162        let main_span = rir.find_function(interner.get_or_intern("main")).unwrap();
3163
3164        // Get a view of main's instructions
3165        let view = rir.function_view(main_span);
3166
3167        // The view should contain the right number of instructions
3168        assert_eq!(view.len(), main_span.instruction_count() as usize);
3169
3170        // The last instruction should be the FnDecl
3171        let fn_decl = view.fn_decl();
3172        match &fn_decl.data {
3173            InstData::FnDecl { name, .. } => {
3174                assert_eq!(interner.resolve(name), "main");
3175            }
3176            _ => panic!("Expected FnDecl"),
3177        }
3178
3179        // We should be able to iterate over the view
3180        let mut found_call = false;
3181        for (_, inst) in view.iter() {
3182            if matches!(inst.data, InstData::Call { .. }) {
3183                found_call = true;
3184            }
3185        }
3186        assert!(found_call, "main should contain a call to helper");
3187    }
3188
3189    #[test]
3190    fn test_function_span_complex_body() {
3191        let source = r#"
3192            fn complex() -> i32 {
3193                let x = 1;
3194                let y = 2;
3195                if x < y {
3196                    x + y
3197                } else {
3198                    x - y
3199                }
3200            }
3201        "#;
3202        let (rir, interner) = gen_rir(source);
3203
3204        assert_eq!(rir.function_count(), 1);
3205
3206        let span = rir
3207            .find_function(interner.get_or_intern("complex"))
3208            .unwrap();
3209
3210        // The function should have multiple instructions for the body
3211        // At minimum: 2 IntConsts, 2 Allocs, comparison, branches, operations, FnDecl
3212        assert!(
3213            span.instruction_count() >= 8,
3214            "Complex function should have at least 8 instructions, got {}",
3215            span.instruction_count()
3216        );
3217
3218        // Verify the view contains all expected instruction types
3219        let view = rir.function_view(span);
3220        let mut has_alloc = false;
3221        let mut has_branch = false;
3222
3223        for (_, inst) in view.iter() {
3224            if matches!(inst.data, InstData::Alloc { .. }) {
3225                has_alloc = true;
3226            }
3227            if matches!(inst.data, InstData::Branch { .. }) {
3228                has_branch = true;
3229            }
3230        }
3231
3232        assert!(has_alloc, "Function should have Alloc instructions");
3233        assert!(has_branch, "Function should have Branch instruction");
3234    }
3235
3236    #[test]
3237    fn test_find_function() {
3238        let source = r#"
3239            fn foo() -> i32 { 1 }
3240            fn bar() -> i32 { 2 }
3241            fn baz() -> i32 { 3 }
3242        "#;
3243        let (rir, interner) = gen_rir(source);
3244
3245        // Find existing functions
3246        let foo_sym = interner.get_or_intern("foo");
3247        let bar_sym = interner.get_or_intern("bar");
3248        let baz_sym = interner.get_or_intern("baz");
3249        let nonexistent_sym = interner.get_or_intern("nonexistent");
3250
3251        assert!(rir.find_function(foo_sym).is_some());
3252        assert!(rir.find_function(bar_sym).is_some());
3253        assert!(rir.find_function(baz_sym).is_some());
3254        assert!(rir.find_function(nonexistent_sym).is_none());
3255    }
3256
3257    #[test]
3258    fn test_function_span_ordering() {
3259        let source = r#"
3260            fn a() -> i32 { 1 }
3261            fn b() -> i32 { 2 }
3262            fn c() -> i32 { 3 }
3263        "#;
3264        let (rir, _interner) = gen_rir(source);
3265
3266        let spans: Vec<_> = rir.functions().collect();
3267        assert_eq!(spans.len(), 3);
3268
3269        // Verify functions are recorded in source order
3270        for i in 1..spans.len() {
3271            assert!(
3272                spans[i - 1].decl.as_u32() < spans[i].body_start.as_u32(),
3273                "Function {} should end before function {} starts",
3274                i - 1,
3275                i
3276            );
3277        }
3278    }
3279
3280    #[test]
3281    fn test_anon_struct_with_methods() {
3282        // Test that anonymous structs with methods generate AnonStructType with method references
3283        let source = r#"
3284            fn MakePoint(comptime T: type) -> type {
3285                struct {
3286                    x: T,
3287                    y: T,
3288
3289                    fn get_x(self) -> T { self.x }
3290                    fn origin() -> Self { Self { x: 0, y: 0 } }
3291                }
3292            }
3293            fn main() -> i32 { 0 }
3294        "#;
3295        let (rir, interner) = gen_rir(source);
3296
3297        // Find the AnonStructType instruction
3298        let anon_struct = rir
3299            .iter()
3300            .find(|(_, inst)| matches!(inst.data, InstData::AnonStructType { .. }));
3301        assert!(
3302            anon_struct.is_some(),
3303            "Expected to find AnonStructType instruction"
3304        );
3305
3306        let (_, inst) = anon_struct.unwrap();
3307        match &inst.data {
3308            InstData::AnonStructType {
3309                fields_start,
3310                fields_len,
3311                methods_start,
3312                methods_len,
3313                ..
3314            } => {
3315                // Should have 2 fields (x and y)
3316                let fields = rir.get_field_decls(*fields_start, *fields_len);
3317                assert_eq!(fields.len(), 2);
3318                assert_eq!(interner.resolve(&fields[0].0), "x");
3319                assert_eq!(interner.resolve(&fields[1].0), "y");
3320
3321                // Should have 2 methods (get_x and origin)
3322                assert_eq!(*methods_len, 2);
3323                let methods = rir.get_inst_refs(*methods_start, *methods_len);
3324                assert_eq!(methods.len(), 2);
3325
3326                // Verify each method is a FnDecl
3327                for method_ref in methods {
3328                    let method_inst = rir.get(method_ref);
3329                    match &method_inst.data {
3330                        InstData::FnDecl { name, has_self, .. } => {
3331                            let name_str = interner.resolve(name);
3332                            // get_x has self, origin doesn't
3333                            if name_str == "get_x" {
3334                                assert!(*has_self, "get_x should have self parameter");
3335                            } else if name_str == "origin" {
3336                                assert!(!*has_self, "origin should not have self parameter");
3337                            }
3338                        }
3339                        _ => panic!("Expected FnDecl for method"),
3340                    }
3341                }
3342            }
3343            _ => panic!("Expected AnonStructType"),
3344        }
3345    }
3346
3347    #[test]
3348    fn test_anon_struct_without_methods() {
3349        // Test that anonymous structs without methods have zero methods_len
3350        let source = r#"
3351            fn MakePair(comptime T: type) -> type {
3352                struct { first: T, second: T }
3353            }
3354            fn main() -> i32 { 0 }
3355        "#;
3356        let (rir, _interner) = gen_rir(source);
3357
3358        // Find the AnonStructType instruction
3359        let anon_struct = rir
3360            .iter()
3361            .find(|(_, inst)| matches!(inst.data, InstData::AnonStructType { .. }));
3362        assert!(
3363            anon_struct.is_some(),
3364            "Expected to find AnonStructType instruction"
3365        );
3366
3367        let (_, inst) = anon_struct.unwrap();
3368        match &inst.data {
3369            InstData::AnonStructType { methods_len, .. } => {
3370                assert_eq!(*methods_len, 0, "Expected no methods");
3371            }
3372            _ => panic!("Expected AnonStructType"),
3373        }
3374    }
3375
3376    #[test]
3377    fn test_anon_struct_method_function_spans() {
3378        // Test that methods inside anonymous structs are tracked in function_spans
3379        let source = r#"
3380            fn Container(comptime T: type) -> type {
3381                struct {
3382                    value: T,
3383                    fn get(self) -> T { self.value }
3384                    fn set(self, v: T) -> Self { Self { value: v } }
3385                }
3386            }
3387            fn main() -> i32 { 0 }
3388        "#;
3389        let (rir, interner) = gen_rir(source);
3390
3391        // Should have 4 functions: Container, get, set, main
3392        assert_eq!(
3393            rir.function_count(),
3394            4,
3395            "Expected 4 functions (Container, get, set, main)"
3396        );
3397
3398        // Check that all methods are findable by name
3399        let get_sym = interner.get_or_intern("get");
3400        let set_sym = interner.get_or_intern("set");
3401        assert!(
3402            rir.find_function(get_sym).is_some(),
3403            "Should find 'get' method"
3404        );
3405        assert!(
3406            rir.find_function(set_sym).is_some(),
3407            "Should find 'set' method"
3408        );
3409    }
3410
3411    // ========================================================================
3412    // ADR-0055 Phase 2: anonymous function RIR lowering
3413    // ========================================================================
3414
3415    #[test]
3416    fn test_anon_fn_lowers_to_call_method_and_value() {
3417        // `fn(x: i32) -> i32 { x + 1 }` should produce an internal FnDecl
3418        // named `__call` plus an `AnonFnValue` referring to it.
3419        let (rir, interner) = gen_rir("fn main() -> i32 { fn(x: i32) -> i32 { x + 1 }; 0 }");
3420
3421        let call_sym = interner.get("__call").expect("__call should be interned");
3422
3423        // Locate the synthesized __call method.
3424        let mut found_call = None;
3425        let mut found_anon_fn_value = None;
3426        for (i, inst) in rir.iter() {
3427            match &inst.data {
3428                InstData::FnDecl { name, has_self, .. } if *name == call_sym => {
3429                    assert!(*has_self, "synthesized __call must have self receiver");
3430                    found_call = Some(i);
3431                }
3432                InstData::AnonFnValue { method } => {
3433                    found_anon_fn_value = Some((i, *method));
3434                }
3435                _ => {}
3436            }
3437        }
3438
3439        let call_ref = found_call.expect("expected a FnDecl named __call in RIR");
3440        let (_, value_method) = found_anon_fn_value.expect("expected an AnonFnValue in RIR");
3441        assert_eq!(
3442            value_method, call_ref,
3443            "AnonFnValue must point at the synthesized __call FnDecl"
3444        );
3445    }
3446
3447    #[test]
3448    fn test_anon_fn_two_sites_each_get_their_own_call_method() {
3449        // Two distinct anonymous-function expressions produce two distinct
3450        // __call FnDecl instructions (one per site), even with matching
3451        // signatures. This is important for Phase 3's uniqueness work — each
3452        // site's body must survive into RIR separately.
3453        let (rir, interner) = gen_rir(
3454            "fn main() -> i32 {
3455                fn(x: i32) -> i32 { x + 1 };
3456                fn(x: i32) -> i32 { x * 2 };
3457                0
3458            }",
3459        );
3460        let call_sym = interner.get("__call").expect("__call should be interned");
3461        let count = rir
3462            .iter()
3463            .filter(|(_, inst)| matches!(&inst.data, InstData::FnDecl { name, .. } if *name == call_sym))
3464            .count();
3465        assert_eq!(
3466            count, 2,
3467            "each fn(...) site should produce its own __call FnDecl"
3468        );
3469    }
3470
3471    #[test]
3472    fn test_anon_fn_zero_params_lowers() {
3473        let (rir, interner) = gen_rir("fn main() -> i32 { fn() -> i32 { 7 }; 0 }");
3474        let call_sym = interner.get("__call").expect("__call should be interned");
3475        let call_inst = rir.iter().find_map(|(_, inst)| {
3476            if let InstData::FnDecl {
3477                name,
3478                params_len,
3479                has_self,
3480                ..
3481            } = &inst.data
3482                && *name == call_sym
3483            {
3484                Some((*params_len, *has_self))
3485            } else {
3486                None
3487            }
3488        });
3489        let (params_len, has_self) = call_inst.expect("expected __call FnDecl");
3490        assert_eq!(params_len, 0);
3491        assert!(has_self);
3492    }
3493}