gruel_rir/
inst.rs

1//! RIR instruction definitions.
2//!
3//! Instructions are stored in a dense array and referenced by index.
4//! This provides good cache locality and efficient traversal.
5
6use std::fmt;
7
8use gruel_span::Span;
9use lasso::{Key, Spur};
10
11/// A reference to an instruction in the RIR.
12///
13/// This is a lightweight handle (4 bytes) that indexes into the instruction array.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct InstRef(u32);
16
17impl InstRef {
18    /// Create an instruction reference from a raw index.
19    #[inline]
20    pub const fn from_raw(index: u32) -> Self {
21        Self(index)
22    }
23
24    /// Get the raw index.
25    #[inline]
26    pub const fn as_u32(self) -> u32 {
27        self.0
28    }
29}
30
31/// A directive in the RIR (e.g., @allow(unused_variable))
32#[derive(Debug, Clone)]
33pub struct RirDirective {
34    /// Directive name (e.g., "allow")
35    pub name: Spur,
36    /// Arguments (e.g., ["unused_variable"])
37    pub args: Vec<Spur>,
38    /// Span covering the directive
39    pub span: Span,
40}
41
42/// Parameter passing mode in RIR.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
44pub enum RirParamMode {
45    /// Normal pass-by-value parameter
46    #[default]
47    Normal,
48    /// Inout parameter - mutated in place and returned to caller
49    Inout,
50    /// Borrow parameter - immutable borrow without ownership transfer
51    Borrow,
52    /// Comptime parameter - evaluated at compile time (used for type parameters)
53    Comptime,
54}
55
56/// A parameter in a function declaration.
57#[derive(Debug, Clone)]
58pub struct RirParam {
59    /// Parameter name
60    pub name: Spur,
61    /// Parameter type
62    pub ty: Spur,
63    /// Parameter passing mode
64    pub mode: RirParamMode,
65    /// Whether this parameter is evaluated at compile time
66    pub is_comptime: bool,
67}
68
69/// Argument passing mode in RIR.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
71pub enum RirArgMode {
72    /// Normal pass-by-value argument
73    #[default]
74    Normal,
75    /// Inout argument - mutated in place
76    Inout,
77    /// Borrow argument - immutable borrow
78    Borrow,
79}
80
81/// An argument in a function call.
82#[derive(Debug, Clone)]
83pub struct RirCallArg {
84    /// The argument expression
85    pub value: InstRef,
86    /// The passing mode for this argument
87    pub mode: RirArgMode,
88}
89
90impl RirCallArg {
91    /// Returns true if this argument is passed as inout.
92    /// This is a convenience method for backwards compatibility.
93    pub fn is_inout(&self) -> bool {
94        self.mode == RirArgMode::Inout
95    }
96
97    /// Returns true if this argument is passed as borrow.
98    pub fn is_borrow(&self) -> bool {
99        self.mode == RirArgMode::Borrow
100    }
101}
102
103/// A pattern in a match expression (RIR level - untyped).
104#[derive(Debug, Clone)]
105pub enum RirPattern {
106    /// Wildcard pattern `_` - matches anything
107    Wildcard(Span),
108    /// Integer literal pattern (can be positive or negative)
109    Int(i64, Span),
110    /// Boolean literal pattern
111    Bool(bool, Span),
112    /// Path pattern for enum variants (e.g., `Color::Red` or `module.Color::Red`)
113    Path {
114        /// Optional module reference for qualified paths (e.g., the `module` in `module.Color::Red`)
115        module: Option<InstRef>,
116        /// The enum type name
117        type_name: Spur,
118        /// The variant name
119        variant: Spur,
120        /// Span of the pattern
121        span: Span,
122    },
123    /// Data variant pattern with field bindings (e.g., `Option::Some(x)`)
124    DataVariant {
125        /// Optional module reference for qualified paths
126        module: Option<InstRef>,
127        /// The enum type name
128        type_name: Spur,
129        /// The variant name
130        variant: Spur,
131        /// Bindings for each field
132        bindings: Vec<RirPatternBinding>,
133        /// Span of the pattern
134        span: Span,
135    },
136    /// Struct variant pattern with named field bindings (e.g., `Shape::Circle { radius }`)
137    StructVariant {
138        /// Optional module reference for qualified paths
139        module: Option<InstRef>,
140        /// The enum type name
141        type_name: Spur,
142        /// The variant name
143        variant: Spur,
144        /// Named field bindings
145        field_bindings: Vec<RirStructPatternBinding>,
146        /// Span of the pattern
147        span: Span,
148    },
149}
150
151/// A binding in a data variant pattern.
152#[derive(Debug, Clone)]
153pub struct RirPatternBinding {
154    /// Whether this is a wildcard binding (`_`)
155    pub is_wildcard: bool,
156    /// Whether this is a mutable binding (only meaningful if not wildcard)
157    pub is_mut: bool,
158    /// The binding name (None for wildcard bindings)
159    pub name: Option<Spur>,
160}
161
162/// A named field binding in a struct variant pattern.
163#[derive(Debug, Clone)]
164pub struct RirStructPatternBinding {
165    /// The field name being matched
166    pub field_name: Spur,
167    /// The binding for this field
168    pub binding: RirPatternBinding,
169}
170
171impl RirPattern {
172    /// Get the span of this pattern.
173    pub fn span(&self) -> Span {
174        match self {
175            RirPattern::Wildcard(span) => *span,
176            RirPattern::Int(_, span) => *span,
177            RirPattern::Bool(_, span) => *span,
178            RirPattern::Path { span, .. } => *span,
179            RirPattern::DataVariant { span, .. } => *span,
180            RirPattern::StructVariant { span, .. } => *span,
181        }
182    }
183}
184
185/// Extra data marker types for type-safe storage in the extra array.
186/// These types represent data stored in the extra array.
187/// Stored representation of RirCallArg in the extra array.
188/// Layout: [value: u32, mode: u32] = 2 u32s per arg
189const CALL_ARG_SIZE: u32 = 2;
190
191/// Stored representation of RirParam in the extra array.
192/// Layout: [name: u32, ty: u32, mode: u32, is_comptime: u32] = 4 u32s per param
193const PARAM_SIZE: u32 = 4;
194
195/// Stored representation of match arm in the extra array.
196/// Layout: pattern data + [body: u32]
197/// Pattern data varies by kind (see PatternKind enum).
198/// Pattern kinds encoded in extra array
199#[repr(u32)]
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum PatternKind {
202    /// Wildcard pattern: [kind, span_start, span_len]
203    Wildcard = 0,
204    /// Int pattern: [kind, span_start, span_len, value_lo, value_hi]
205    Int = 1,
206    /// Bool pattern: [kind, span_start, span_len, value]
207    Bool = 2,
208    /// Path pattern: [kind, span_start, span_len, module, type_name, variant]
209    /// module is u32::MAX for None, otherwise an InstRef
210    Path = 3,
211    /// Data variant pattern: [kind, span_start, span_len, module, type_name, variant, body, bindings_len, (flags, name)...]
212    /// Each binding is 2 u32s: flags (bit0=is_wildcard, bit1=is_mut), name (Spur, u32::MAX if wildcard)
213    DataVariant = 4,
214    /// Struct variant pattern: [kind, span_start, span_len, module, type_name, variant, body, bindings_len, (field_name, flags, binding_name)...]
215    /// Each field binding is 3 u32s: field_name (Spur), flags (bit0=is_wildcard, bit1=is_mut), binding_name (Spur, u32::MAX if wildcard)
216    StructVariant = 5,
217}
218
219/// Size of each pattern kind in the extra array (including body InstRef)
220const PATTERN_WILDCARD_SIZE: u32 = 4; // kind, span_start, span_len, body
221const PATTERN_INT_SIZE: u32 = 6; // kind, span_start, span_len, value_lo, value_hi, body
222const PATTERN_BOOL_SIZE: u32 = 5; // kind, span_start, span_len, value, body
223const PATTERN_PATH_SIZE: u32 = 7; // kind, span_start, span_len, module, type_name, variant, body
224// DataVariant size: 8 + 2 * bindings_len (variable)
225
226/// Stored representation of a destructure field in the extra array.
227/// Layout: [field_name: u32, binding_name: u32 (0 = shorthand), is_wildcard: u32, is_mut: u32]
228const DESTRUCTURE_FIELD_SIZE: u32 = 4;
229
230/// A decoded destructure field from the extra array.
231#[derive(Debug, Clone)]
232pub struct RirDestructureField {
233    /// The struct field being bound
234    pub field_name: Spur,
235    /// Binding name (None for shorthand or wildcard)
236    pub binding_name: Option<Spur>,
237    /// Whether this is a wildcard binding (`field: _`)
238    pub is_wildcard: bool,
239    /// Whether the binding is mutable
240    pub is_mut: bool,
241}
242
243/// Stored representation of struct field initializer.
244/// Layout: [field_name: u32, value: u32] = 2 u32s per field
245const FIELD_INIT_SIZE: u32 = 2;
246
247/// Stored representation of struct field declaration.
248/// Layout: [field_name: u32, field_type: u32] = 2 u32s per field
249const FIELD_DECL_SIZE: u32 = 2;
250
251/// Stored representation of directive in the extra array.
252/// Layout: [name: u32, span_start: u32, span_len: u32, args_len: u32, args...]
253/// Variable size due to args.
254/// A span marking the boundaries of a function in the RIR.
255///
256/// This allows efficient per-function analysis by identifying which instructions
257/// belong to each function without scanning the entire instruction array.
258#[derive(Debug, Clone)]
259pub struct FunctionSpan {
260    /// Function name symbol
261    pub name: Spur,
262    /// Index of the first instruction of this function's body.
263    /// This is the first instruction generated for the function's expressions/statements.
264    pub body_start: InstRef,
265    /// Index of the FnDecl instruction for this function.
266    /// This is always the last instruction of the function.
267    pub decl: InstRef,
268}
269
270impl FunctionSpan {
271    /// Create a new function span.
272    pub fn new(name: Spur, body_start: InstRef, decl: InstRef) -> Self {
273        Self {
274            name,
275            body_start,
276            decl,
277        }
278    }
279
280    /// Get the number of instructions in this function (including the FnDecl).
281    pub fn instruction_count(&self) -> u32 {
282        self.decl.as_u32() - self.body_start.as_u32() + 1
283    }
284}
285
286/// A view into a function's instructions within the RIR.
287///
288/// This provides a way to iterate over just the instructions belonging to a
289/// specific function, enabling per-function analysis without copying data.
290#[derive(Debug)]
291pub struct RirFunctionView<'a> {
292    rir: &'a Rir,
293    body_start: InstRef,
294    decl: InstRef,
295}
296
297impl<'a> RirFunctionView<'a> {
298    /// Get the instruction at the given reference.
299    ///
300    /// Note: The reference must be within this function's range.
301    #[inline]
302    pub fn get(&self, inst_ref: InstRef) -> &'a Inst {
303        debug_assert!(
304            inst_ref.as_u32() >= self.body_start.as_u32()
305                && inst_ref.as_u32() <= self.decl.as_u32(),
306            "InstRef {} is outside function range [{}, {}]",
307            inst_ref,
308            self.body_start,
309            self.decl
310        );
311        self.rir.get(inst_ref)
312    }
313
314    /// Get the FnDecl instruction for this function.
315    #[inline]
316    pub fn fn_decl(&self) -> &'a Inst {
317        self.rir.get(self.decl)
318    }
319
320    /// Iterate over all instructions in this function (including FnDecl).
321    pub fn iter(&self) -> impl Iterator<Item = (InstRef, &'a Inst)> {
322        let start = self.body_start.as_u32();
323        let end = self.decl.as_u32() + 1;
324        (start..end).map(move |i| {
325            let inst_ref = InstRef::from_raw(i);
326            (inst_ref, self.rir.get(inst_ref))
327        })
328    }
329
330    /// Get the number of instructions in this function view.
331    pub fn len(&self) -> usize {
332        (self.decl.as_u32() - self.body_start.as_u32() + 1) as usize
333    }
334
335    /// Whether this view is empty (should never be true for valid functions).
336    pub fn is_empty(&self) -> bool {
337        self.body_start.as_u32() > self.decl.as_u32()
338    }
339
340    /// Access the underlying RIR for operations that need the full context
341    /// (e.g., accessing extra data).
342    pub fn rir(&self) -> &'a Rir {
343        self.rir
344    }
345}
346
347/// The complete RIR for a source file.
348#[derive(Debug, Default)]
349pub struct Rir {
350    /// All instructions in the file
351    instructions: Vec<Inst>,
352    /// Extra data for variable-length instruction payloads
353    extra: Vec<u32>,
354    /// Function boundaries for per-function analysis
355    function_spans: Vec<FunctionSpan>,
356}
357
358impl Rir {
359    /// Create a new empty RIR.
360    pub fn new() -> Self {
361        Self::default()
362    }
363
364    /// Add an instruction and return its reference.
365    pub fn add_inst(&mut self, inst: Inst) -> InstRef {
366        // Debug assertion for u32 overflow - catches pathological inputs during development
367        debug_assert!(
368            self.instructions.len() < u32::MAX as usize,
369            "RIR instruction count overflow: {} instructions exceeds u32::MAX - 1",
370            self.instructions.len()
371        );
372
373        let index = self.instructions.len() as u32;
374        self.instructions.push(inst);
375        InstRef::from_raw(index)
376    }
377
378    /// Get an instruction by reference.
379    #[inline]
380    pub fn get(&self, inst_ref: InstRef) -> &Inst {
381        &self.instructions[inst_ref.0 as usize]
382    }
383
384    /// Get a mutable reference to an instruction.
385    #[inline]
386    pub fn get_mut(&mut self, inst_ref: InstRef) -> &mut Inst {
387        &mut self.instructions[inst_ref.0 as usize]
388    }
389
390    /// The number of instructions.
391    #[inline]
392    pub fn len(&self) -> usize {
393        self.instructions.len()
394    }
395
396    /// Whether there are no instructions.
397    #[inline]
398    pub fn is_empty(&self) -> bool {
399        self.instructions.is_empty()
400    }
401
402    /// Iterate over all instructions with their references.
403    pub fn iter(&self) -> impl Iterator<Item = (InstRef, &Inst)> {
404        self.instructions
405            .iter()
406            .enumerate()
407            .map(|(i, inst)| (InstRef::from_raw(i as u32), inst))
408    }
409
410    /// Add extra data and return the start index.
411    pub fn add_extra(&mut self, data: &[u32]) -> u32 {
412        // Debug assertions for u32 overflow - catches pathological inputs during development
413        debug_assert!(
414            self.extra.len() <= u32::MAX as usize,
415            "RIR extra data overflow: {} entries exceeds u32::MAX",
416            self.extra.len()
417        );
418        debug_assert!(
419            self.extra.len().saturating_add(data.len()) <= u32::MAX as usize,
420            "RIR extra data would overflow: {} + {} exceeds u32::MAX",
421            self.extra.len(),
422            data.len()
423        );
424
425        let start = self.extra.len() as u32;
426        self.extra.extend_from_slice(data);
427        start
428    }
429
430    /// Get extra data by index.
431    #[inline]
432    pub fn get_extra(&self, start: u32, len: u32) -> &[u32] {
433        let start = start as usize;
434        let end = start + len as usize;
435        &self.extra[start..end]
436    }
437
438    // ===== Helper methods for storing/retrieving typed data in the extra array =====
439
440    /// Store a slice of InstRefs and return (start, len).
441    pub fn add_inst_refs(&mut self, refs: &[InstRef]) -> (u32, u32) {
442        let data: Vec<u32> = refs.iter().map(|r| r.as_u32()).collect();
443        let start = self.add_extra(&data);
444        (start, refs.len() as u32)
445    }
446
447    /// Retrieve InstRefs from the extra array.
448    pub fn get_inst_refs(&self, start: u32, len: u32) -> Vec<InstRef> {
449        self.get_extra(start, len)
450            .iter()
451            .map(|&v| InstRef::from_raw(v))
452            .collect()
453    }
454
455    /// Store a slice of Spurs and return (start, len).
456    pub fn add_symbols(&mut self, symbols: &[Spur]) -> (u32, u32) {
457        let data: Vec<u32> = symbols.iter().map(|s| s.into_usize() as u32).collect();
458        let start = self.add_extra(&data);
459        (start, symbols.len() as u32)
460    }
461
462    /// Retrieve Spurs from the extra array.
463    pub fn get_symbols(&self, start: u32, len: u32) -> Vec<Spur> {
464        self.get_extra(start, len)
465            .iter()
466            .map(|&v| Spur::try_from_usize(v as usize).unwrap())
467            .collect()
468    }
469
470    /// Store enum variant declarations and return (start, variant_count).
471    ///
472    /// Each variant is encoded as variable-length data in the extra array:
473    ///   `[name_spur, field_count, is_struct, field_type_0, ..., field_type_n, field_name_0?, ..., field_name_n?]`
474    ///
475    /// - `is_struct`: 0 for unit/tuple variants, 1 for struct variants.
476    /// - For struct variants, field names follow field types.
477    /// - Unit variants have `field_count = 0`.
478    pub fn add_enum_variant_decls(
479        &mut self,
480        variants: &[(Spur, Vec<Spur>, Vec<Spur>)],
481    ) -> (u32, u32) {
482        let start = self.extra.len() as u32;
483        for (name, field_types, field_names) in variants {
484            self.extra.push(name.into_usize() as u32);
485            self.extra.push(field_types.len() as u32);
486            let is_struct = if field_names.is_empty() { 0u32 } else { 1u32 };
487            self.extra.push(is_struct);
488            for field_ty in field_types {
489                self.extra.push(field_ty.into_usize() as u32);
490            }
491            for field_name in field_names {
492                self.extra.push(field_name.into_usize() as u32);
493            }
494        }
495        (start, variants.len() as u32)
496    }
497
498    /// Retrieve enum variant declarations from the extra array.
499    /// Returns a vec of `(variant_name, field_types, field_names)` triples.
500    /// `field_names` is empty for unit/tuple variants.
501    pub fn get_enum_variant_decls(
502        &self,
503        start: u32,
504        variant_count: u32,
505    ) -> Vec<(Spur, Vec<Spur>, Vec<Spur>)> {
506        let mut result = Vec::with_capacity(variant_count as usize);
507        let mut pos = start as usize;
508        for _ in 0..variant_count {
509            let name = Spur::try_from_usize(self.extra[pos] as usize).unwrap();
510            let field_count = self.extra[pos + 1] as usize;
511            let is_struct = self.extra[pos + 2] != 0;
512            pos += 3;
513            let field_types: Vec<Spur> = (0..field_count)
514                .map(|i| Spur::try_from_usize(self.extra[pos + i] as usize).unwrap())
515                .collect();
516            pos += field_count;
517            let field_names = if is_struct {
518                let names: Vec<Spur> = (0..field_count)
519                    .map(|i| Spur::try_from_usize(self.extra[pos + i] as usize).unwrap())
520                    .collect();
521                pos += field_count;
522                names
523            } else {
524                Vec::new()
525            };
526            result.push((name, field_types, field_names));
527        }
528        result
529    }
530
531    /// Store RirCallArgs and return (start, len).
532    /// Layout: [value: u32, mode: u32] per arg
533    pub fn add_call_args(&mut self, args: &[RirCallArg]) -> (u32, u32) {
534        let mut data = Vec::with_capacity(args.len() * CALL_ARG_SIZE as usize);
535        for arg in args {
536            data.push(arg.value.as_u32());
537            data.push(arg.mode as u32);
538        }
539        let start = self.add_extra(&data);
540        (start, args.len() as u32)
541    }
542
543    /// Retrieve RirCallArgs from the extra array.
544    pub fn get_call_args(&self, start: u32, len: u32) -> Vec<RirCallArg> {
545        let data = self.get_extra(start, len * CALL_ARG_SIZE);
546        let mut args = Vec::with_capacity(len as usize);
547        for chunk in data.chunks(CALL_ARG_SIZE as usize) {
548            let value = InstRef::from_raw(chunk[0]);
549            let mode = match chunk[1] {
550                0 => RirArgMode::Normal,
551                1 => RirArgMode::Inout,
552                2 => RirArgMode::Borrow,
553                _ => RirArgMode::Normal, // Fallback, shouldn't happen
554            };
555            args.push(RirCallArg { value, mode });
556        }
557        args
558    }
559
560    /// Store RirParams and return (start, len).
561    /// Layout: [name: u32, ty: u32, mode: u32, is_comptime: u32] per param
562    pub fn add_params(&mut self, params: &[RirParam]) -> (u32, u32) {
563        let mut data = Vec::with_capacity(params.len() * PARAM_SIZE as usize);
564        for param in params {
565            data.push(param.name.into_usize() as u32);
566            data.push(param.ty.into_usize() as u32);
567            data.push(param.mode as u32);
568            data.push(param.is_comptime as u32);
569        }
570        let start = self.add_extra(&data);
571        (start, params.len() as u32)
572    }
573
574    /// Retrieve RirParams from the extra array.
575    pub fn get_params(&self, start: u32, len: u32) -> Vec<RirParam> {
576        let data = self.get_extra(start, len * PARAM_SIZE);
577        let mut params = Vec::with_capacity(len as usize);
578        for chunk in data.chunks(PARAM_SIZE as usize) {
579            let name = Spur::try_from_usize(chunk[0] as usize).unwrap();
580            let ty = Spur::try_from_usize(chunk[1] as usize).unwrap();
581            let mode = match chunk[2] {
582                0 => RirParamMode::Normal,
583                1 => RirParamMode::Inout,
584                2 => RirParamMode::Borrow,
585                3 => RirParamMode::Comptime,
586                _ => RirParamMode::Normal, // Fallback
587            };
588            let is_comptime = chunk[3] != 0;
589            params.push(RirParam {
590                name,
591                ty,
592                mode,
593                is_comptime,
594            });
595        }
596        params
597    }
598
599    /// Store match arms (pattern + body pairs) and return (start, arm_count).
600    /// Each arm is stored with variable size depending on pattern kind.
601    pub fn add_match_arms(&mut self, arms: &[(RirPattern, InstRef)]) -> (u32, u32) {
602        let start = self.extra.len() as u32;
603        for (pattern, body) in arms {
604            match pattern {
605                RirPattern::Wildcard(span) => {
606                    self.extra.push(PatternKind::Wildcard as u32);
607                    self.extra.push(span.start());
608                    self.extra.push(span.len());
609                    self.extra.push(body.as_u32());
610                }
611                RirPattern::Int(value, span) => {
612                    self.extra.push(PatternKind::Int as u32);
613                    self.extra.push(span.start());
614                    self.extra.push(span.len());
615                    // Store i64 as two u32s (little-endian)
616                    self.extra.push(*value as u32);
617                    self.extra.push((*value >> 32) as u32);
618                    self.extra.push(body.as_u32());
619                }
620                RirPattern::Bool(value, span) => {
621                    self.extra.push(PatternKind::Bool as u32);
622                    self.extra.push(span.start());
623                    self.extra.push(span.len());
624                    self.extra.push(if *value { 1 } else { 0 });
625                    self.extra.push(body.as_u32());
626                }
627                RirPattern::Path {
628                    module,
629                    type_name,
630                    variant,
631                    span,
632                } => {
633                    self.extra.push(PatternKind::Path as u32);
634                    self.extra.push(span.start());
635                    self.extra.push(span.len());
636                    // Store module as u32::MAX for None, otherwise the InstRef
637                    self.extra.push(module.map_or(u32::MAX, |r| r.as_u32()));
638                    self.extra.push(type_name.into_usize() as u32);
639                    self.extra.push(variant.into_usize() as u32);
640                    self.extra.push(body.as_u32());
641                }
642                RirPattern::DataVariant {
643                    module,
644                    type_name,
645                    variant,
646                    bindings,
647                    span,
648                } => {
649                    self.extra.push(PatternKind::DataVariant as u32);
650                    self.extra.push(span.start());
651                    self.extra.push(span.len());
652                    self.extra.push(module.map_or(u32::MAX, |r| r.as_u32()));
653                    self.extra.push(type_name.into_usize() as u32);
654                    self.extra.push(variant.into_usize() as u32);
655                    self.extra.push(body.as_u32());
656                    self.extra.push(bindings.len() as u32);
657                    for binding in bindings {
658                        let flags = if binding.is_wildcard { 1u32 } else { 0 }
659                            | if binding.is_mut { 2 } else { 0 };
660                        self.extra.push(flags);
661                        self.extra
662                            .push(binding.name.map_or(u32::MAX, |s| s.into_usize() as u32));
663                    }
664                }
665                RirPattern::StructVariant {
666                    module,
667                    type_name,
668                    variant,
669                    field_bindings,
670                    span,
671                } => {
672                    self.extra.push(PatternKind::StructVariant as u32);
673                    self.extra.push(span.start());
674                    self.extra.push(span.len());
675                    self.extra.push(module.map_or(u32::MAX, |r| r.as_u32()));
676                    self.extra.push(type_name.into_usize() as u32);
677                    self.extra.push(variant.into_usize() as u32);
678                    self.extra.push(body.as_u32());
679                    self.extra.push(field_bindings.len() as u32);
680                    for fb in field_bindings {
681                        self.extra.push(fb.field_name.into_usize() as u32);
682                        let flags = if fb.binding.is_wildcard { 1u32 } else { 0 }
683                            | if fb.binding.is_mut { 2 } else { 0 };
684                        self.extra.push(flags);
685                        self.extra
686                            .push(fb.binding.name.map_or(u32::MAX, |s| s.into_usize() as u32));
687                    }
688                }
689            }
690        }
691        (start, arms.len() as u32)
692    }
693
694    /// Retrieve match arms from the extra array.
695    pub fn get_match_arms(&self, start: u32, arm_count: u32) -> Vec<(RirPattern, InstRef)> {
696        let mut arms = Vec::with_capacity(arm_count as usize);
697        let mut pos = start as usize;
698
699        for _ in 0..arm_count {
700            let kind = self.extra[pos];
701            match kind {
702                k if k == PatternKind::Wildcard as u32 => {
703                    let span_start = self.extra[pos + 1];
704                    let span_len = self.extra[pos + 2];
705                    let span = Span::new(span_start, span_start + span_len);
706                    let body = InstRef::from_raw(self.extra[pos + 3]);
707                    arms.push((RirPattern::Wildcard(span), body));
708                    pos += PATTERN_WILDCARD_SIZE as usize;
709                }
710                k if k == PatternKind::Int as u32 => {
711                    let span_start = self.extra[pos + 1];
712                    let span_len = self.extra[pos + 2];
713                    let span = Span::new(span_start, span_start + span_len);
714                    let value_lo = self.extra[pos + 3] as i64;
715                    let value_hi = self.extra[pos + 4] as i64;
716                    let value = value_lo | (value_hi << 32);
717                    let body = InstRef::from_raw(self.extra[pos + 5]);
718                    arms.push((RirPattern::Int(value, span), body));
719                    pos += PATTERN_INT_SIZE as usize;
720                }
721                k if k == PatternKind::Bool as u32 => {
722                    let span_start = self.extra[pos + 1];
723                    let span_len = self.extra[pos + 2];
724                    let span = Span::new(span_start, span_start + span_len);
725                    let value = self.extra[pos + 3] != 0;
726                    let body = InstRef::from_raw(self.extra[pos + 4]);
727                    arms.push((RirPattern::Bool(value, span), body));
728                    pos += PATTERN_BOOL_SIZE as usize;
729                }
730                k if k == PatternKind::Path as u32 => {
731                    let span_start = self.extra[pos + 1];
732                    let span_len = self.extra[pos + 2];
733                    let span = Span::new(span_start, span_start + span_len);
734                    // Decode module: u32::MAX means None
735                    let module_raw = self.extra[pos + 3];
736                    let module = if module_raw == u32::MAX {
737                        None
738                    } else {
739                        Some(InstRef::from_raw(module_raw))
740                    };
741                    let type_name = Spur::try_from_usize(self.extra[pos + 4] as usize).unwrap();
742                    let variant = Spur::try_from_usize(self.extra[pos + 5] as usize).unwrap();
743                    let body = InstRef::from_raw(self.extra[pos + 6]);
744                    arms.push((
745                        RirPattern::Path {
746                            module,
747                            type_name,
748                            variant,
749                            span,
750                        },
751                        body,
752                    ));
753                    pos += PATTERN_PATH_SIZE as usize;
754                }
755                k if k == PatternKind::DataVariant as u32 => {
756                    let span_start = self.extra[pos + 1];
757                    let span_len = self.extra[pos + 2];
758                    let span = Span::new(span_start, span_start + span_len);
759                    let module_raw = self.extra[pos + 3];
760                    let module = if module_raw == u32::MAX {
761                        None
762                    } else {
763                        Some(InstRef::from_raw(module_raw))
764                    };
765                    let type_name = Spur::try_from_usize(self.extra[pos + 4] as usize).unwrap();
766                    let variant = Spur::try_from_usize(self.extra[pos + 5] as usize).unwrap();
767                    let body = InstRef::from_raw(self.extra[pos + 6]);
768                    let bindings_len = self.extra[pos + 7] as usize;
769                    let mut bindings = Vec::with_capacity(bindings_len);
770                    for i in 0..bindings_len {
771                        let flags = self.extra[pos + 8 + i * 2];
772                        let name_raw = self.extra[pos + 9 + i * 2];
773                        let is_wildcard = flags & 1 != 0;
774                        let is_mut = flags & 2 != 0;
775                        let name = if name_raw == u32::MAX {
776                            None
777                        } else {
778                            Some(Spur::try_from_usize(name_raw as usize).unwrap())
779                        };
780                        bindings.push(RirPatternBinding {
781                            is_wildcard,
782                            is_mut,
783                            name,
784                        });
785                    }
786                    arms.push((
787                        RirPattern::DataVariant {
788                            module,
789                            type_name,
790                            variant,
791                            bindings,
792                            span,
793                        },
794                        body,
795                    ));
796                    pos += 8 + 2 * bindings_len;
797                }
798                k if k == PatternKind::StructVariant as u32 => {
799                    let span_start = self.extra[pos + 1];
800                    let span_len = self.extra[pos + 2];
801                    let span = Span::new(span_start, span_start + span_len);
802                    let module_raw = self.extra[pos + 3];
803                    let module = if module_raw == u32::MAX {
804                        None
805                    } else {
806                        Some(InstRef::from_raw(module_raw))
807                    };
808                    let type_name = Spur::try_from_usize(self.extra[pos + 4] as usize).unwrap();
809                    let variant = Spur::try_from_usize(self.extra[pos + 5] as usize).unwrap();
810                    let body = InstRef::from_raw(self.extra[pos + 6]);
811                    let bindings_len = self.extra[pos + 7] as usize;
812                    let mut field_bindings = Vec::with_capacity(bindings_len);
813                    for i in 0..bindings_len {
814                        let field_name =
815                            Spur::try_from_usize(self.extra[pos + 8 + i * 3] as usize).unwrap();
816                        let flags = self.extra[pos + 9 + i * 3];
817                        let name_raw = self.extra[pos + 10 + i * 3];
818                        let is_wildcard = flags & 1 != 0;
819                        let is_mut = flags & 2 != 0;
820                        let name = if name_raw == u32::MAX {
821                            None
822                        } else {
823                            Some(Spur::try_from_usize(name_raw as usize).unwrap())
824                        };
825                        field_bindings.push(RirStructPatternBinding {
826                            field_name,
827                            binding: RirPatternBinding {
828                                is_wildcard,
829                                is_mut,
830                                name,
831                            },
832                        });
833                    }
834                    arms.push((
835                        RirPattern::StructVariant {
836                            module,
837                            type_name,
838                            variant,
839                            field_bindings,
840                            span,
841                        },
842                        body,
843                    ));
844                    pos += 8 + 3 * bindings_len;
845                }
846                _ => panic!("Unknown pattern kind: {}", kind),
847            }
848        }
849        arms
850    }
851
852    /// Store field initializers (name, value) and return (start, len).
853    /// Layout: [name: u32, value: u32] per field
854    pub fn add_field_inits(&mut self, fields: &[(Spur, InstRef)]) -> (u32, u32) {
855        let mut data = Vec::with_capacity(fields.len() * FIELD_INIT_SIZE as usize);
856        for (name, value) in fields {
857            data.push(name.into_usize() as u32);
858            data.push(value.as_u32());
859        }
860        let start = self.add_extra(&data);
861        (start, fields.len() as u32)
862    }
863
864    /// Retrieve field initializers from the extra array.
865    pub fn get_field_inits(&self, start: u32, len: u32) -> Vec<(Spur, InstRef)> {
866        let data = self.get_extra(start, len * FIELD_INIT_SIZE);
867        let mut fields = Vec::with_capacity(len as usize);
868        for chunk in data.chunks(FIELD_INIT_SIZE as usize) {
869            let name = Spur::try_from_usize(chunk[0] as usize).unwrap();
870            let value = InstRef::from_raw(chunk[1]);
871            fields.push((name, value));
872        }
873        fields
874    }
875
876    /// Store field declarations (name, type) and return (start, len).
877    /// Layout: [name: u32, type: u32] per field
878    pub fn add_field_decls(&mut self, fields: &[(Spur, Spur)]) -> (u32, u32) {
879        let mut data = Vec::with_capacity(fields.len() * FIELD_DECL_SIZE as usize);
880        for (name, ty) in fields {
881            data.push(name.into_usize() as u32);
882            data.push(ty.into_usize() as u32);
883        }
884        let start = self.add_extra(&data);
885        (start, fields.len() as u32)
886    }
887
888    /// Retrieve field declarations from the extra array.
889    pub fn get_field_decls(&self, start: u32, len: u32) -> Vec<(Spur, Spur)> {
890        let data = self.get_extra(start, len * FIELD_DECL_SIZE);
891        let mut fields = Vec::with_capacity(len as usize);
892        for chunk in data.chunks(FIELD_DECL_SIZE as usize) {
893            let name = Spur::try_from_usize(chunk[0] as usize).unwrap();
894            let ty = Spur::try_from_usize(chunk[1] as usize).unwrap();
895            fields.push((name, ty));
896        }
897        fields
898    }
899
900    /// Store directives and return (start, directive_count).
901    /// Layout: [name: u32, span_start: u32, span_len: u32, args_len: u32, args...] per directive
902    pub fn add_directives(&mut self, directives: &[RirDirective]) -> (u32, u32) {
903        let start = self.extra.len() as u32;
904        for directive in directives {
905            self.extra.push(directive.name.into_usize() as u32);
906            self.extra.push(directive.span.start());
907            self.extra.push(directive.span.len());
908            self.extra.push(directive.args.len() as u32);
909            for arg in &directive.args {
910                self.extra.push(arg.into_usize() as u32);
911            }
912        }
913        (start, directives.len() as u32)
914    }
915
916    /// Retrieve directives from the extra array.
917    pub fn get_directives(&self, start: u32, directive_count: u32) -> Vec<RirDirective> {
918        let mut directives = Vec::with_capacity(directive_count as usize);
919        let mut pos = start as usize;
920
921        for _ in 0..directive_count {
922            let name = Spur::try_from_usize(self.extra[pos] as usize).unwrap();
923            let span = Span::new(self.extra[pos + 1], self.extra[pos + 2]);
924            let args_len = self.extra[pos + 3] as usize;
925            pos += 4;
926
927            let args: Vec<Spur> = (0..args_len)
928                .map(|i| Spur::try_from_usize(self.extra[pos + i] as usize).unwrap())
929                .collect();
930            pos += args_len;
931
932            directives.push(RirDirective { name, args, span });
933        }
934        directives
935    }
936
937    /// Store destructure fields and return (start, field_count).
938    pub fn add_destructure_fields(&mut self, fields: &[RirDestructureField]) -> (u32, u32) {
939        let start = self.extra.len() as u32;
940        for field in fields {
941            self.extra.push(field.field_name.into_usize() as u32);
942            self.extra
943                .push(field.binding_name.map_or(0, |s| s.into_usize() as u32));
944            self.extra.push(field.is_wildcard as u32);
945            self.extra.push(field.is_mut as u32);
946        }
947        (start, fields.len() as u32)
948    }
949
950    /// Retrieve destructure fields from the extra array.
951    pub fn get_destructure_fields(&self, start: u32, count: u32) -> Vec<RirDestructureField> {
952        let mut fields = Vec::with_capacity(count as usize);
953        for i in 0..count {
954            let pos = (start + i * DESTRUCTURE_FIELD_SIZE) as usize;
955            let field_name = Spur::try_from_usize(self.extra[pos] as usize).unwrap();
956            let binding_raw = self.extra[pos + 1];
957            let binding_name = if binding_raw == 0 {
958                None
959            } else {
960                Some(Spur::try_from_usize(binding_raw as usize).unwrap())
961            };
962            let is_wildcard = self.extra[pos + 2] != 0;
963            let is_mut = self.extra[pos + 3] != 0;
964            fields.push(RirDestructureField {
965                field_name,
966                binding_name,
967                is_wildcard,
968                is_mut,
969            });
970        }
971        fields
972    }
973
974    // ===== Function span methods =====
975
976    /// Add a function span to track function boundaries.
977    pub fn add_function_span(&mut self, span: FunctionSpan) {
978        self.function_spans.push(span);
979    }
980
981    /// Get all function spans.
982    pub fn function_spans(&self) -> &[FunctionSpan] {
983        &self.function_spans
984    }
985
986    /// Iterate over function spans.
987    pub fn functions(&self) -> impl Iterator<Item = &FunctionSpan> {
988        self.function_spans.iter()
989    }
990
991    /// Get the number of functions.
992    pub fn function_count(&self) -> usize {
993        self.function_spans.len()
994    }
995
996    /// Get a view of just one function's instructions.
997    pub fn function_view(&self, fn_span: &FunctionSpan) -> RirFunctionView<'_> {
998        RirFunctionView {
999            rir: self,
1000            body_start: fn_span.body_start,
1001            decl: fn_span.decl,
1002        }
1003    }
1004
1005    /// Find a function span by name.
1006    pub fn find_function(&self, name: Spur) -> Option<&FunctionSpan> {
1007        self.function_spans.iter().find(|span| span.name == name)
1008    }
1009
1010    /// Get the current instruction count (useful for tracking body start).
1011    pub fn current_inst_index(&self) -> u32 {
1012        self.instructions.len() as u32
1013    }
1014
1015    /// Merge multiple RIRs into a single RIR.
1016    ///
1017    /// This is used for parallel per-file RIR generation. Each file generates
1018    /// its own RIR in parallel, then they are merged into a single RIR with
1019    /// all instruction references renumbered to be valid in the merged RIR.
1020    ///
1021    /// # Arguments
1022    ///
1023    /// * `rirs` - Slice of RIRs to merge (typically one per source file)
1024    ///
1025    /// # Returns
1026    ///
1027    /// A new merged RIR containing all instructions from all input RIRs.
1028    pub fn merge(rirs: &[Rir]) -> Rir {
1029        if rirs.is_empty() {
1030            return Rir::new();
1031        }
1032
1033        if rirs.len() == 1 {
1034            // Clone the single RIR directly
1035            return Rir {
1036                instructions: rirs[0].instructions.clone(),
1037                extra: rirs[0].extra.clone(),
1038                function_spans: rirs[0].function_spans.clone(),
1039            };
1040        }
1041
1042        // Calculate total sizes for preallocation
1043        let total_instructions: usize = rirs.iter().map(|r| r.instructions.len()).sum();
1044        let total_extra: usize = rirs.iter().map(|r| r.extra.len()).sum();
1045        let total_functions: usize = rirs.iter().map(|r| r.function_spans.len()).sum();
1046
1047        let mut merged = Rir {
1048            instructions: Vec::with_capacity(total_instructions),
1049            extra: Vec::with_capacity(total_extra),
1050            function_spans: Vec::with_capacity(total_functions),
1051        };
1052
1053        // Track offsets as we merge each RIR
1054        let mut inst_offset: u32 = 0;
1055        let mut extra_offset: u32 = 0;
1056
1057        for rir in rirs {
1058            // Merge extra data first (append raw bytes)
1059            merged.extra.extend_from_slice(&rir.extra);
1060
1061            // Merge and renumber instructions
1062            for inst in &rir.instructions {
1063                let renumbered = Self::renumber_instruction(inst, inst_offset, extra_offset);
1064                merged.instructions.push(renumbered);
1065            }
1066
1067            // Renumber InstRefs in the extra array
1068            // This handles call args, match arms, field inits, etc.
1069            Self::renumber_extra_inst_refs(
1070                &mut merged.extra,
1071                &rir.instructions,
1072                inst_offset,
1073                extra_offset,
1074            );
1075
1076            // Merge function spans with renumbered references
1077            for fn_span in &rir.function_spans {
1078                merged.function_spans.push(FunctionSpan {
1079                    name: fn_span.name,
1080                    body_start: InstRef::from_raw(fn_span.body_start.as_u32() + inst_offset),
1081                    decl: InstRef::from_raw(fn_span.decl.as_u32() + inst_offset),
1082                });
1083            }
1084
1085            // Update offsets for the next RIR
1086            inst_offset += rir.instructions.len() as u32;
1087            extra_offset += rir.extra.len() as u32;
1088        }
1089
1090        merged
1091    }
1092
1093    /// Renumber a single instruction's InstRef fields.
1094    fn renumber_instruction(inst: &Inst, inst_offset: u32, extra_offset: u32) -> Inst {
1095        let renumber = |r: InstRef| InstRef::from_raw(r.as_u32() + inst_offset);
1096        let renumber_opt = |r: Option<InstRef>| r.map(renumber);
1097
1098        let data = match &inst.data {
1099            // No renumbering needed for these
1100            InstData::IntConst(v) => InstData::IntConst(*v),
1101            InstData::FloatConst(bits) => InstData::FloatConst(*bits),
1102            InstData::BoolConst(v) => InstData::BoolConst(*v),
1103            InstData::StringConst(s) => InstData::StringConst(*s),
1104            InstData::UnitConst => InstData::UnitConst,
1105            InstData::Break => InstData::Break,
1106            InstData::Continue => InstData::Continue,
1107            InstData::VarRef { name } => InstData::VarRef { name: *name },
1108            InstData::ParamRef { index, name } => InstData::ParamRef {
1109                index: *index,
1110                name: *name,
1111            },
1112            InstData::EnumVariant {
1113                module,
1114                type_name,
1115                variant,
1116            } => InstData::EnumVariant {
1117                module: module.map(renumber),
1118                type_name: *type_name,
1119                variant: *variant,
1120            },
1121            InstData::EnumStructVariant {
1122                module,
1123                type_name,
1124                variant,
1125                fields_start,
1126                fields_len,
1127            } => InstData::EnumStructVariant {
1128                module: module.map(renumber),
1129                type_name: *type_name,
1130                variant: *variant,
1131                fields_start: *fields_start,
1132                fields_len: *fields_len,
1133            },
1134            InstData::TypeIntrinsic { name, type_arg } => InstData::TypeIntrinsic {
1135                name: *name,
1136                type_arg: *type_arg,
1137            },
1138
1139            // Binary operations - renumber both operands
1140            InstData::Add { lhs, rhs } => InstData::Add {
1141                lhs: renumber(*lhs),
1142                rhs: renumber(*rhs),
1143            },
1144            InstData::Sub { lhs, rhs } => InstData::Sub {
1145                lhs: renumber(*lhs),
1146                rhs: renumber(*rhs),
1147            },
1148            InstData::Mul { lhs, rhs } => InstData::Mul {
1149                lhs: renumber(*lhs),
1150                rhs: renumber(*rhs),
1151            },
1152            InstData::Div { lhs, rhs } => InstData::Div {
1153                lhs: renumber(*lhs),
1154                rhs: renumber(*rhs),
1155            },
1156            InstData::Mod { lhs, rhs } => InstData::Mod {
1157                lhs: renumber(*lhs),
1158                rhs: renumber(*rhs),
1159            },
1160            InstData::Eq { lhs, rhs } => InstData::Eq {
1161                lhs: renumber(*lhs),
1162                rhs: renumber(*rhs),
1163            },
1164            InstData::Ne { lhs, rhs } => InstData::Ne {
1165                lhs: renumber(*lhs),
1166                rhs: renumber(*rhs),
1167            },
1168            InstData::Lt { lhs, rhs } => InstData::Lt {
1169                lhs: renumber(*lhs),
1170                rhs: renumber(*rhs),
1171            },
1172            InstData::Gt { lhs, rhs } => InstData::Gt {
1173                lhs: renumber(*lhs),
1174                rhs: renumber(*rhs),
1175            },
1176            InstData::Le { lhs, rhs } => InstData::Le {
1177                lhs: renumber(*lhs),
1178                rhs: renumber(*rhs),
1179            },
1180            InstData::Ge { lhs, rhs } => InstData::Ge {
1181                lhs: renumber(*lhs),
1182                rhs: renumber(*rhs),
1183            },
1184            InstData::And { lhs, rhs } => InstData::And {
1185                lhs: renumber(*lhs),
1186                rhs: renumber(*rhs),
1187            },
1188            InstData::Or { lhs, rhs } => InstData::Or {
1189                lhs: renumber(*lhs),
1190                rhs: renumber(*rhs),
1191            },
1192            InstData::BitAnd { lhs, rhs } => InstData::BitAnd {
1193                lhs: renumber(*lhs),
1194                rhs: renumber(*rhs),
1195            },
1196            InstData::BitOr { lhs, rhs } => InstData::BitOr {
1197                lhs: renumber(*lhs),
1198                rhs: renumber(*rhs),
1199            },
1200            InstData::BitXor { lhs, rhs } => InstData::BitXor {
1201                lhs: renumber(*lhs),
1202                rhs: renumber(*rhs),
1203            },
1204            InstData::Shl { lhs, rhs } => InstData::Shl {
1205                lhs: renumber(*lhs),
1206                rhs: renumber(*rhs),
1207            },
1208            InstData::Shr { lhs, rhs } => InstData::Shr {
1209                lhs: renumber(*lhs),
1210                rhs: renumber(*rhs),
1211            },
1212
1213            // Unary operations
1214            InstData::Neg { operand } => InstData::Neg {
1215                operand: renumber(*operand),
1216            },
1217            InstData::Not { operand } => InstData::Not {
1218                operand: renumber(*operand),
1219            },
1220            InstData::BitNot { operand } => InstData::BitNot {
1221                operand: renumber(*operand),
1222            },
1223
1224            // Control flow
1225            InstData::Branch {
1226                cond,
1227                then_block,
1228                else_block,
1229            } => InstData::Branch {
1230                cond: renumber(*cond),
1231                then_block: renumber(*then_block),
1232                else_block: renumber_opt(*else_block),
1233            },
1234            InstData::Loop { cond, body } => InstData::Loop {
1235                cond: renumber(*cond),
1236                body: renumber(*body),
1237            },
1238            InstData::For {
1239                binding,
1240                is_mut,
1241                iterable,
1242                body,
1243            } => InstData::For {
1244                binding: *binding,
1245                is_mut: *is_mut,
1246                iterable: renumber(*iterable),
1247                body: renumber(*body),
1248            },
1249            InstData::InfiniteLoop { body } => InstData::InfiniteLoop {
1250                body: renumber(*body),
1251            },
1252            InstData::Ret(value) => InstData::Ret(renumber_opt(*value)),
1253
1254            // Match - InstRefs in extra are handled separately
1255            InstData::Match {
1256                scrutinee,
1257                arms_start,
1258                arms_len,
1259            } => InstData::Match {
1260                scrutinee: renumber(*scrutinee),
1261                arms_start: *arms_start + extra_offset,
1262                arms_len: *arms_len,
1263            },
1264
1265            // Block - InstRefs in extra are handled separately
1266            InstData::Block { extra_start, len } => InstData::Block {
1267                extra_start: *extra_start + extra_offset,
1268                len: *len,
1269            },
1270
1271            // Variable operations
1272            InstData::Alloc {
1273                directives_start,
1274                directives_len,
1275                name,
1276                is_mut,
1277                ty,
1278                init,
1279            } => InstData::Alloc {
1280                directives_start: *directives_start + extra_offset,
1281                directives_len: *directives_len,
1282                name: *name,
1283                is_mut: *is_mut,
1284                ty: *ty,
1285                init: renumber(*init),
1286            },
1287            InstData::StructDestructure {
1288                type_name,
1289                fields_start,
1290                fields_len,
1291                init,
1292            } => InstData::StructDestructure {
1293                type_name: *type_name,
1294                fields_start: *fields_start + extra_offset,
1295                fields_len: *fields_len,
1296                init: renumber(*init),
1297            },
1298            InstData::Assign { name, value } => InstData::Assign {
1299                name: *name,
1300                value: renumber(*value),
1301            },
1302
1303            // Function definition - body and params in extra
1304            InstData::FnDecl {
1305                directives_start,
1306                directives_len,
1307                is_pub,
1308                is_unchecked,
1309                name,
1310                params_start,
1311                params_len,
1312                return_type,
1313                body,
1314                has_self,
1315            } => InstData::FnDecl {
1316                directives_start: *directives_start + extra_offset,
1317                directives_len: *directives_len,
1318                is_pub: *is_pub,
1319                is_unchecked: *is_unchecked,
1320                name: *name,
1321                params_start: *params_start + extra_offset,
1322                params_len: *params_len,
1323                return_type: *return_type,
1324                body: renumber(*body),
1325                has_self: *has_self,
1326            },
1327
1328            // Constant declaration - init is an InstRef
1329            InstData::ConstDecl {
1330                directives_start,
1331                directives_len,
1332                is_pub,
1333                name,
1334                ty,
1335                init,
1336            } => InstData::ConstDecl {
1337                directives_start: *directives_start + extra_offset,
1338                directives_len: *directives_len,
1339                is_pub: *is_pub,
1340                name: *name,
1341                ty: *ty,
1342                init: renumber(*init),
1343            },
1344
1345            // Function call - args in extra
1346            InstData::Call {
1347                name,
1348                args_start,
1349                args_len,
1350            } => InstData::Call {
1351                name: *name,
1352                args_start: *args_start + extra_offset,
1353                args_len: *args_len,
1354            },
1355
1356            // Intrinsic - args in extra
1357            InstData::Intrinsic {
1358                name,
1359                args_start,
1360                args_len,
1361            } => InstData::Intrinsic {
1362                name: *name,
1363                args_start: *args_start + extra_offset,
1364                args_len: *args_len,
1365            },
1366
1367            // Struct operations
1368            InstData::StructDecl {
1369                directives_start,
1370                directives_len,
1371                is_pub,
1372                is_linear,
1373                name,
1374                fields_start,
1375                fields_len,
1376                methods_start,
1377                methods_len,
1378            } => InstData::StructDecl {
1379                directives_start: *directives_start + extra_offset,
1380                directives_len: *directives_len,
1381                is_pub: *is_pub,
1382                is_linear: *is_linear,
1383                name: *name,
1384                fields_start: *fields_start + extra_offset,
1385                fields_len: *fields_len,
1386                methods_start: *methods_start + extra_offset,
1387                methods_len: *methods_len,
1388            },
1389            InstData::StructInit {
1390                module,
1391                type_name,
1392                fields_start,
1393                fields_len,
1394            } => InstData::StructInit {
1395                module: module.map(renumber),
1396                type_name: *type_name,
1397                fields_start: *fields_start + extra_offset,
1398                fields_len: *fields_len,
1399            },
1400            InstData::FieldGet { base, field } => InstData::FieldGet {
1401                base: renumber(*base),
1402                field: *field,
1403            },
1404            InstData::FieldSet { base, field, value } => InstData::FieldSet {
1405                base: renumber(*base),
1406                field: *field,
1407                value: renumber(*value),
1408            },
1409
1410            // Enum operations
1411            InstData::EnumDecl {
1412                is_pub,
1413                name,
1414                variants_start,
1415                variants_len,
1416            } => InstData::EnumDecl {
1417                is_pub: *is_pub,
1418                name: *name,
1419                variants_start: *variants_start + extra_offset,
1420                variants_len: *variants_len,
1421            },
1422
1423            // Array operations
1424            InstData::ArrayInit {
1425                elems_start,
1426                elems_len,
1427            } => InstData::ArrayInit {
1428                elems_start: *elems_start + extra_offset,
1429                elems_len: *elems_len,
1430            },
1431            InstData::IndexGet { base, index } => InstData::IndexGet {
1432                base: renumber(*base),
1433                index: renumber(*index),
1434            },
1435            InstData::IndexSet { base, index, value } => InstData::IndexSet {
1436                base: renumber(*base),
1437                index: renumber(*index),
1438                value: renumber(*value),
1439            },
1440
1441            // Method operations
1442            InstData::MethodCall {
1443                receiver,
1444                method,
1445                args_start,
1446                args_len,
1447            } => InstData::MethodCall {
1448                receiver: renumber(*receiver),
1449                method: *method,
1450                args_start: *args_start + extra_offset,
1451                args_len: *args_len,
1452            },
1453            InstData::AssocFnCall {
1454                type_name,
1455                function,
1456                args_start,
1457                args_len,
1458            } => InstData::AssocFnCall {
1459                type_name: *type_name,
1460                function: *function,
1461                args_start: *args_start + extra_offset,
1462                args_len: *args_len,
1463            },
1464            InstData::DropFnDecl { type_name, body } => InstData::DropFnDecl {
1465                type_name: *type_name,
1466                body: renumber(*body),
1467            },
1468            InstData::Comptime { expr } => InstData::Comptime {
1469                expr: renumber(*expr),
1470            },
1471            InstData::ComptimeUnrollFor {
1472                binding,
1473                iterable,
1474                body,
1475            } => InstData::ComptimeUnrollFor {
1476                binding: *binding,
1477                iterable: renumber(*iterable),
1478                body: renumber(*body),
1479            },
1480            InstData::Checked { expr } => InstData::Checked {
1481                expr: renumber(*expr),
1482            },
1483            InstData::TypeConst { type_name } => InstData::TypeConst {
1484                type_name: *type_name,
1485            },
1486            InstData::AnonStructType {
1487                fields_start,
1488                fields_len,
1489                methods_start,
1490                methods_len,
1491            } => InstData::AnonStructType {
1492                fields_start: *fields_start + extra_offset,
1493                fields_len: *fields_len,
1494                methods_start: *methods_start + extra_offset,
1495                methods_len: *methods_len,
1496            },
1497            InstData::AnonEnumType {
1498                variants_start,
1499                variants_len,
1500                methods_start,
1501                methods_len,
1502            } => InstData::AnonEnumType {
1503                variants_start: *variants_start + extra_offset,
1504                variants_len: *variants_len,
1505                methods_start: *methods_start + extra_offset,
1506                methods_len: *methods_len,
1507            },
1508        };
1509
1510        Inst {
1511            data,
1512            span: inst.span,
1513        }
1514    }
1515
1516    /// Renumber InstRefs stored in the extra array.
1517    ///
1518    /// This handles:
1519    /// - Block instruction refs (simple u32 array)
1520    /// - Array init element refs (simple u32 array)
1521    /// - Intrinsic arg refs (simple u32 array)
1522    /// - Impl method refs (simple u32 array)
1523    /// - Call args (value field of each arg)
1524    /// - Field inits (value field of each init)
1525    /// - Match arm bodies (last field of each arm)
1526    fn renumber_extra_inst_refs(
1527        extra: &mut [u32],
1528        instructions: &[Inst],
1529        inst_offset: u32,
1530        extra_offset: u32,
1531    ) {
1532        for inst in instructions {
1533            match &inst.data {
1534                // Block - contains InstRef array
1535                InstData::Block { extra_start, len } => {
1536                    let start = (*extra_start + extra_offset) as usize;
1537                    for i in 0..*len as usize {
1538                        extra[start + i] += inst_offset;
1539                    }
1540                }
1541
1542                // Array init - contains InstRef array
1543                InstData::ArrayInit {
1544                    elems_start,
1545                    elems_len,
1546                } => {
1547                    let start = (*elems_start + extra_offset) as usize;
1548                    for i in 0..*elems_len as usize {
1549                        extra[start + i] += inst_offset;
1550                    }
1551                }
1552
1553                // Intrinsic - contains InstRef array
1554                InstData::Intrinsic {
1555                    args_start,
1556                    args_len,
1557                    ..
1558                } => {
1559                    let start = (*args_start + extra_offset) as usize;
1560                    for i in 0..*args_len as usize {
1561                        extra[start + i] += inst_offset;
1562                    }
1563                }
1564
1565                // Struct decl - contains InstRef array for methods
1566                InstData::StructDecl {
1567                    methods_start,
1568                    methods_len,
1569                    ..
1570                } => {
1571                    let start = (*methods_start + extra_offset) as usize;
1572                    for i in 0..*methods_len as usize {
1573                        extra[start + i] += inst_offset;
1574                    }
1575                }
1576
1577                // Anonymous struct type - contains InstRef array for methods
1578                InstData::AnonStructType {
1579                    methods_start,
1580                    methods_len,
1581                    ..
1582                } => {
1583                    let start = (*methods_start + extra_offset) as usize;
1584                    for i in 0..*methods_len as usize {
1585                        extra[start + i] += inst_offset;
1586                    }
1587                }
1588
1589                // Anonymous enum type - contains InstRef array for methods
1590                InstData::AnonEnumType {
1591                    methods_start,
1592                    methods_len,
1593                    ..
1594                } => {
1595                    let start = (*methods_start + extra_offset) as usize;
1596                    for i in 0..*methods_len as usize {
1597                        extra[start + i] += inst_offset;
1598                    }
1599                }
1600
1601                // Call args - layout: [value, mode] pairs
1602                InstData::Call {
1603                    args_start,
1604                    args_len,
1605                    ..
1606                }
1607                | InstData::MethodCall {
1608                    args_start,
1609                    args_len,
1610                    ..
1611                }
1612                | InstData::AssocFnCall {
1613                    args_start,
1614                    args_len,
1615                    ..
1616                } => {
1617                    let start = (*args_start + extra_offset) as usize;
1618                    for i in 0..*args_len as usize {
1619                        // Each arg is 2 u32s: [value, mode]
1620                        extra[start + i * 2] += inst_offset;
1621                    }
1622                }
1623
1624                // Field inits - layout: [name, value] pairs
1625                InstData::StructInit {
1626                    fields_start,
1627                    fields_len,
1628                    ..
1629                }
1630                | InstData::EnumStructVariant {
1631                    fields_start,
1632                    fields_len,
1633                    ..
1634                } => {
1635                    let start = (*fields_start + extra_offset) as usize;
1636                    for i in 0..*fields_len as usize {
1637                        // Each field is 2 u32s: [name, value]
1638                        extra[start + i * 2 + 1] += inst_offset;
1639                    }
1640                }
1641
1642                // Match arms - variable size patterns with body InstRef at end
1643                InstData::Match {
1644                    arms_start,
1645                    arms_len,
1646                    ..
1647                } => {
1648                    let mut pos = (*arms_start + extra_offset) as usize;
1649                    for _ in 0..*arms_len {
1650                        let kind = extra[pos];
1651                        let pattern_size = match kind {
1652                            k if k == PatternKind::Wildcard as u32 => {
1653                                PATTERN_WILDCARD_SIZE as usize
1654                            }
1655                            k if k == PatternKind::Int as u32 => PATTERN_INT_SIZE as usize,
1656                            k if k == PatternKind::Bool as u32 => PATTERN_BOOL_SIZE as usize,
1657                            k if k == PatternKind::Path as u32 => PATTERN_PATH_SIZE as usize,
1658                            _ => panic!("Unknown pattern kind during merge: {}", kind),
1659                        };
1660                        // The body InstRef is always the last element of each pattern
1661                        extra[pos + pattern_size - 1] += inst_offset;
1662                        pos += pattern_size;
1663                    }
1664                }
1665
1666                // These don't have InstRefs in extra
1667                InstData::IntConst(_)
1668                | InstData::FloatConst(_)
1669                | InstData::BoolConst(_)
1670                | InstData::StringConst(_)
1671                | InstData::UnitConst
1672                | InstData::Add { .. }
1673                | InstData::Sub { .. }
1674                | InstData::Mul { .. }
1675                | InstData::Div { .. }
1676                | InstData::Mod { .. }
1677                | InstData::Eq { .. }
1678                | InstData::Ne { .. }
1679                | InstData::Lt { .. }
1680                | InstData::Gt { .. }
1681                | InstData::Le { .. }
1682                | InstData::Ge { .. }
1683                | InstData::And { .. }
1684                | InstData::Or { .. }
1685                | InstData::BitAnd { .. }
1686                | InstData::BitOr { .. }
1687                | InstData::BitXor { .. }
1688                | InstData::Shl { .. }
1689                | InstData::Shr { .. }
1690                | InstData::Neg { .. }
1691                | InstData::Not { .. }
1692                | InstData::BitNot { .. }
1693                | InstData::Branch { .. }
1694                | InstData::Loop { .. }
1695                | InstData::For { .. }
1696                | InstData::InfiniteLoop { .. }
1697                | InstData::Break
1698                | InstData::Continue
1699                | InstData::Ret(_)
1700                | InstData::VarRef { .. }
1701                | InstData::ParamRef { .. }
1702                | InstData::Alloc { .. }
1703                | InstData::Assign { .. }
1704                | InstData::FnDecl { .. }
1705                | InstData::ConstDecl { .. }
1706                | InstData::FieldGet { .. }
1707                | InstData::FieldSet { .. }
1708                | InstData::EnumDecl { .. }
1709                | InstData::EnumVariant { .. }
1710                | InstData::IndexGet { .. }
1711                | InstData::IndexSet { .. }
1712                | InstData::TypeIntrinsic { .. }
1713                | InstData::DropFnDecl { .. }
1714                | InstData::Comptime { .. }
1715                | InstData::ComptimeUnrollFor { .. }
1716                | InstData::Checked { .. }
1717                | InstData::TypeConst { .. }
1718                | InstData::StructDestructure { .. } => {}
1719            }
1720        }
1721    }
1722}
1723
1724/// A single RIR instruction.
1725#[derive(Debug, Clone)]
1726pub struct Inst {
1727    pub data: InstData,
1728    pub span: Span,
1729}
1730
1731/// Instruction data - the actual operation.
1732#[derive(Debug, Clone)]
1733pub enum InstData {
1734    /// Integer constant
1735    IntConst(u64),
1736
1737    /// Floating-point constant, stored as f64 bits for Eq/Hash/Copy compatibility.
1738    /// Use `f64::from_bits()` to recover the value.
1739    FloatConst(u64),
1740
1741    /// Boolean constant
1742    BoolConst(bool),
1743
1744    /// String constant (interned string content)
1745    StringConst(Spur),
1746
1747    /// Unit constant (for blocks that produce unit type)
1748    UnitConst,
1749
1750    // Binary arithmetic operations
1751    /// Addition: lhs + rhs
1752    Add { lhs: InstRef, rhs: InstRef },
1753    /// Subtraction: lhs - rhs
1754    Sub { lhs: InstRef, rhs: InstRef },
1755    /// Multiplication: lhs * rhs
1756    Mul { lhs: InstRef, rhs: InstRef },
1757    /// Division: lhs / rhs
1758    Div { lhs: InstRef, rhs: InstRef },
1759    /// Modulo: lhs % rhs
1760    Mod { lhs: InstRef, rhs: InstRef },
1761
1762    // Comparison operations
1763    /// Equality: lhs == rhs
1764    Eq { lhs: InstRef, rhs: InstRef },
1765    /// Inequality: lhs != rhs
1766    Ne { lhs: InstRef, rhs: InstRef },
1767    /// Less than: lhs < rhs
1768    Lt { lhs: InstRef, rhs: InstRef },
1769    /// Greater than: lhs > rhs
1770    Gt { lhs: InstRef, rhs: InstRef },
1771    /// Less than or equal: lhs <= rhs
1772    Le { lhs: InstRef, rhs: InstRef },
1773    /// Greater than or equal: lhs >= rhs
1774    Ge { lhs: InstRef, rhs: InstRef },
1775
1776    // Logical operations
1777    /// Logical AND: lhs && rhs
1778    And { lhs: InstRef, rhs: InstRef },
1779    /// Logical OR: lhs || rhs
1780    Or { lhs: InstRef, rhs: InstRef },
1781
1782    // Bitwise operations
1783    /// Bitwise AND: lhs & rhs
1784    BitAnd { lhs: InstRef, rhs: InstRef },
1785    /// Bitwise OR: lhs | rhs
1786    BitOr { lhs: InstRef, rhs: InstRef },
1787    /// Bitwise XOR: lhs ^ rhs
1788    BitXor { lhs: InstRef, rhs: InstRef },
1789    /// Left shift: lhs << rhs
1790    Shl { lhs: InstRef, rhs: InstRef },
1791    /// Right shift: lhs >> rhs (arithmetic for signed, logical for unsigned)
1792    Shr { lhs: InstRef, rhs: InstRef },
1793
1794    // Unary operations
1795    /// Negation: -operand
1796    Neg { operand: InstRef },
1797    /// Logical NOT: !operand
1798    Not { operand: InstRef },
1799    /// Bitwise NOT: ~operand
1800    BitNot { operand: InstRef },
1801
1802    // Control flow
1803    /// Branch: if cond then then_block else else_block
1804    Branch {
1805        cond: InstRef,
1806        then_block: InstRef,
1807        else_block: Option<InstRef>,
1808    },
1809
1810    /// While loop: while cond { body }
1811    Loop { cond: InstRef, body: InstRef },
1812
1813    /// For-in loop: for [mut] binding in iterable { body }
1814    For {
1815        /// The loop variable name
1816        binding: Spur,
1817        /// Whether the loop variable is mutable
1818        is_mut: bool,
1819        /// The iterable expression (array or @range result)
1820        iterable: InstRef,
1821        /// The loop body
1822        body: InstRef,
1823    },
1824
1825    /// Infinite loop: loop { body }
1826    InfiniteLoop { body: InstRef },
1827
1828    /// Match expression: match scrutinee { pattern => expr, ... }
1829    /// Arms are stored in the extra array using add_match_arms/get_match_arms.
1830    Match {
1831        /// The value being matched
1832        scrutinee: InstRef,
1833        /// Index into extra data where arms start
1834        arms_start: u32,
1835        /// Number of match arms
1836        arms_len: u32,
1837    },
1838
1839    /// Break: exits the innermost loop
1840    Break,
1841
1842    /// Continue: jumps to the next iteration of the innermost loop
1843    Continue,
1844
1845    /// Function definition
1846    /// Contains: name symbol, parameters, return type symbol, body instruction ref
1847    /// Directives and params are stored in the extra array.
1848    FnDecl {
1849        /// Index into extra data where directives start
1850        directives_start: u32,
1851        /// Number of directives
1852        directives_len: u32,
1853        /// Whether this function is public (requires --preview modules)
1854        is_pub: bool,
1855        /// Whether this function is marked `unchecked` (can only be called from checked blocks)
1856        is_unchecked: bool,
1857        name: Spur,
1858        /// Index into extra data where params start
1859        params_start: u32,
1860        /// Number of parameters
1861        params_len: u32,
1862        return_type: Spur,
1863        body: InstRef,
1864        /// Whether this function/method takes `self` as a receiver.
1865        /// Only true for methods in impl blocks that have a self parameter.
1866        /// Used by sema to know to add the implicit self parameter.
1867        has_self: bool,
1868    },
1869
1870    /// Constant declaration
1871    /// Contains: name symbol, optional type, initializer expression ref
1872    /// Directives are stored in the extra array.
1873    /// Used for module re-exports: `pub const strings = @import("utils/strings.gruel");`
1874    ConstDecl {
1875        /// Index into extra data where directives start
1876        directives_start: u32,
1877        /// Number of directives
1878        directives_len: u32,
1879        /// Whether this constant is public (requires --preview modules)
1880        is_pub: bool,
1881        /// Constant name
1882        name: Spur,
1883        /// Optional type annotation (interned string, None if inferred)
1884        ty: Option<Spur>,
1885        /// Initializer expression
1886        init: InstRef,
1887    },
1888
1889    /// Function call
1890    /// Args are stored in the extra array using add_call_args/get_call_args.
1891    Call {
1892        /// Function name
1893        name: Spur,
1894        /// Index into extra data where args start
1895        args_start: u32,
1896        /// Number of arguments
1897        args_len: u32,
1898    },
1899
1900    /// Intrinsic call with expression arguments (e.g., @dbg)
1901    /// Args are stored in the extra array using add_inst_refs/get_inst_refs.
1902    Intrinsic {
1903        /// Intrinsic name (without @)
1904        name: Spur,
1905        /// Index into extra data where args start
1906        args_start: u32,
1907        /// Number of arguments
1908        args_len: u32,
1909    },
1910
1911    /// Intrinsic call with a type argument (e.g., @size_of, @align_of)
1912    TypeIntrinsic {
1913        /// Intrinsic name (without @)
1914        name: Spur,
1915        /// Type argument (as an interned string, e.g., "i32", "Point", "[i32; 4]")
1916        type_arg: Spur,
1917    },
1918
1919    /// Reference to a function parameter
1920    ParamRef {
1921        /// Parameter index (0-based)
1922        index: u32,
1923        /// Parameter name (for error messages)
1924        name: Spur,
1925    },
1926
1927    /// Return value from function (None for `return;` in unit-returning functions)
1928    Ret(Option<InstRef>),
1929
1930    /// Block of instructions (for function bodies)
1931    /// The result is the last instruction in the block
1932    Block {
1933        /// Index into extra data where instruction refs start
1934        extra_start: u32,
1935        /// Number of instructions in the block
1936        len: u32,
1937    },
1938
1939    // Variable operations
1940    /// Local variable declaration: allocates storage and initializes
1941    /// If name is None, this is a wildcard pattern that discards the value
1942    /// Directives are stored in the extra array using add_directives/get_directives.
1943    Alloc {
1944        /// Index into extra data where directives start
1945        directives_start: u32,
1946        /// Number of directives
1947        directives_len: u32,
1948        /// Variable name (None for wildcard `_` pattern that discards the value)
1949        name: Option<Spur>,
1950        /// Whether the variable is mutable
1951        is_mut: bool,
1952        /// Optional type annotation
1953        ty: Option<Spur>,
1954        /// Initial value instruction
1955        init: InstRef,
1956    },
1957
1958    /// Struct destructuring: `let TypeName { fields } = expr;`
1959    /// Fields are stored in the extra array as groups of 4 u32s:
1960    /// [field_name, binding_name (0 = shorthand), is_wildcard, is_mut]
1961    StructDestructure {
1962        /// Struct type name
1963        type_name: Spur,
1964        /// Index into extra data where field data starts
1965        fields_start: u32,
1966        /// Number of fields
1967        fields_len: u32,
1968        /// Initializer expression
1969        init: InstRef,
1970    },
1971
1972    /// Variable reference: reads the value of a variable
1973    VarRef {
1974        /// Variable name
1975        name: Spur,
1976    },
1977
1978    /// Assignment: stores a value into a mutable variable
1979    Assign {
1980        /// Variable name
1981        name: Spur,
1982        /// Value to store
1983        value: InstRef,
1984    },
1985
1986    // Struct operations
1987    /// Struct type declaration
1988    /// Directives, fields, and methods are stored in the extra array.
1989    StructDecl {
1990        /// Index into extra data where directives start
1991        directives_start: u32,
1992        /// Number of directives
1993        directives_len: u32,
1994        /// Whether this struct is public (requires --preview modules)
1995        is_pub: bool,
1996        /// Whether this struct is a linear type (must be consumed)
1997        is_linear: bool,
1998        /// Struct name
1999        name: Spur,
2000        /// Index into extra data where fields start
2001        fields_start: u32,
2002        /// Number of fields
2003        fields_len: u32,
2004        /// Index into extra data where method refs start
2005        methods_start: u32,
2006        /// Number of methods
2007        methods_len: u32,
2008    },
2009
2010    /// Struct literal: creates a new struct instance
2011    /// Fields are stored in the extra array using add_field_inits/get_field_inits.
2012    StructInit {
2013        /// Optional module reference (for qualified struct literals like `module.Point { ... }`)
2014        /// If Some, the struct is looked up in the module's exports.
2015        module: Option<InstRef>,
2016        /// Struct type name
2017        type_name: Spur,
2018        /// Index into extra data where fields start
2019        fields_start: u32,
2020        /// Number of fields
2021        fields_len: u32,
2022    },
2023
2024    /// Field access: reads a field from a struct
2025    FieldGet {
2026        /// Base struct value
2027        base: InstRef,
2028        /// Field name
2029        field: Spur,
2030    },
2031
2032    /// Field assignment: writes a value to a struct field
2033    FieldSet {
2034        /// Base struct value
2035        base: InstRef,
2036        /// Field name
2037        field: Spur,
2038        /// Value to store
2039        value: InstRef,
2040    },
2041
2042    // Enum operations
2043    /// Enum type declaration
2044    /// Variants are stored in the extra array using add_enum_variant_decls/get_enum_variant_decls.
2045    /// Each variant encodes: [name_spur, field_count, field_type_0, ..., field_type_n].
2046    EnumDecl {
2047        /// Whether this enum is public (requires --preview modules)
2048        is_pub: bool,
2049        /// Enum name
2050        name: Spur,
2051        /// Index into extra data where variants start
2052        variants_start: u32,
2053        /// Number of variants
2054        variants_len: u32,
2055    },
2056
2057    /// Enum variant: creates a value of an enum type
2058    EnumVariant {
2059        /// Optional module reference (for qualified paths like `module.Color::Red`)
2060        /// If Some, the enum is looked up in the module's exports.
2061        module: Option<InstRef>,
2062        /// Enum type name
2063        type_name: Spur,
2064        /// Variant name
2065        variant: Spur,
2066    },
2067
2068    /// Enum struct variant construction: `Enum::Variant { field: value, ... }`
2069    /// Fields are stored in the extra array using add_field_inits/get_field_inits.
2070    EnumStructVariant {
2071        /// Optional module reference (for qualified paths)
2072        module: Option<InstRef>,
2073        /// Enum type name
2074        type_name: Spur,
2075        /// Variant name
2076        variant: Spur,
2077        /// Start of field initializers in extra array
2078        fields_start: u32,
2079        /// Number of field initializers
2080        fields_len: u32,
2081    },
2082
2083    // Array operations
2084    /// Array literal: creates a new array from element values
2085    /// Elements are stored in the extra array using add_inst_refs/get_inst_refs.
2086    ArrayInit {
2087        /// Index into extra data where elements start
2088        elems_start: u32,
2089        /// Number of elements
2090        elems_len: u32,
2091    },
2092
2093    /// Array index read: reads an element from an array
2094    IndexGet {
2095        /// Base array value
2096        base: InstRef,
2097        /// Index expression
2098        index: InstRef,
2099    },
2100
2101    /// Array index write: writes a value to an array element
2102    IndexSet {
2103        /// Base array value (must be a VarRef to a mutable variable)
2104        base: InstRef,
2105        /// Index expression
2106        index: InstRef,
2107        /// Value to store
2108        value: InstRef,
2109    },
2110
2111    // Method operations
2112    /// Method call: receiver.method(args)
2113    /// Args are stored in the extra array using add_call_args/get_call_args.
2114    MethodCall {
2115        /// Receiver expression (the struct value)
2116        receiver: InstRef,
2117        /// Method name
2118        method: Spur,
2119        /// Index into extra data where args start
2120        args_start: u32,
2121        /// Number of arguments
2122        args_len: u32,
2123    },
2124
2125    /// Associated function call: Type::function(args)
2126    /// Args are stored in the extra array using add_call_args/get_call_args.
2127    AssocFnCall {
2128        /// Type name (e.g., Point)
2129        type_name: Spur,
2130        /// Function name (e.g., origin)
2131        function: Spur,
2132        /// Index into extra data where args start
2133        args_start: u32,
2134        /// Number of arguments
2135        args_len: u32,
2136    },
2137
2138    /// User-defined destructor declaration: drop fn TypeName(self) { ... }
2139    DropFnDecl {
2140        /// The struct type this destructor is for
2141        type_name: Spur,
2142        /// Destructor body instruction ref
2143        body: InstRef,
2144    },
2145
2146    /// Comptime block expression: comptime { expr }
2147    /// The inner expression must be evaluable at compile time.
2148    Comptime {
2149        /// The expression to evaluate at compile time
2150        expr: InstRef,
2151    },
2152
2153    /// Comptime unroll for loop: comptime_unroll for binding in iterable { body }
2154    /// The iterable must be evaluable at compile time. The body is duplicated
2155    /// once per iteration with the binding replaced by each element's value.
2156    ComptimeUnrollFor {
2157        /// The loop variable name
2158        binding: Spur,
2159        /// The iterable expression (must be comptime-evaluable, e.g. @typeInfo fields)
2160        iterable: InstRef,
2161        /// The loop body (will be unrolled at compile time)
2162        body: InstRef,
2163    },
2164
2165    /// Checked block expression: checked { expr }
2166    /// Unchecked operations (raw pointer manipulation, calling unchecked functions)
2167    /// are only allowed inside checked blocks.
2168    Checked {
2169        /// The expression inside the checked block
2170        expr: InstRef,
2171    },
2172
2173    /// Type constant: a type used as a value expression (e.g., `i32` in `identity(i32, 42)`)
2174    /// The type_name is the symbol for the type (e.g., "i32", "bool").
2175    TypeConst {
2176        /// The type name symbol
2177        type_name: Spur,
2178    },
2179
2180    /// Anonymous struct type: a struct type used as a value expression
2181    /// (e.g., `struct { first: T, second: T, fn method(self) -> T { ... } }` in comptime type construction)
2182    /// Fields are stored in the extra array using add_field_decls/get_field_decls.
2183    /// Methods are stored as InstRefs to FnDecl instructions in the extra array.
2184    AnonStructType {
2185        /// Index into extra data where fields start
2186        fields_start: u32,
2187        /// Number of fields
2188        fields_len: u32,
2189        /// Index into extra data where method InstRefs start
2190        methods_start: u32,
2191        /// Number of methods (InstRefs to FnDecl instructions)
2192        methods_len: u32,
2193    },
2194
2195    /// Anonymous enum type: an enum type used as a value expression
2196    /// (e.g., `enum { Some(T), None, fn method(self) -> T { ... } }` in comptime type construction)
2197    /// Variants are stored in the extra array using add_enum_variant_decls/get_enum_variant_decls.
2198    /// Methods are stored as InstRefs to FnDecl instructions in the extra array.
2199    AnonEnumType {
2200        /// Index into extra data where variants start
2201        variants_start: u32,
2202        /// Number of variants
2203        variants_len: u32,
2204        /// Index into extra data where method InstRefs start
2205        methods_start: u32,
2206        /// Number of methods (InstRefs to FnDecl instructions)
2207        methods_len: u32,
2208    },
2209}
2210
2211impl fmt::Display for InstRef {
2212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2213        write!(f, "%{}", self.0)
2214    }
2215}
2216
2217/// Printer for RIR that resolves symbols to their string values.
2218pub struct RirPrinter<'a, 'b> {
2219    rir: &'a Rir,
2220    interner: &'b lasso::ThreadedRodeo,
2221}
2222
2223impl<'a, 'b> RirPrinter<'a, 'b> {
2224    /// Create a new RIR printer.
2225    pub fn new(rir: &'a Rir, interner: &'b lasso::ThreadedRodeo) -> Self {
2226        Self { rir, interner }
2227    }
2228
2229    /// Format a call argument with its mode prefix.
2230    fn format_call_arg(arg: &RirCallArg) -> String {
2231        match arg.mode {
2232            RirArgMode::Inout => format!("inout {}", arg.value),
2233            RirArgMode::Borrow => format!("borrow {}", arg.value),
2234            RirArgMode::Normal => format!("{}", arg.value),
2235        }
2236    }
2237
2238    /// Format a list of call arguments.
2239    fn format_call_args(args: &[RirCallArg]) -> String {
2240        args.iter()
2241            .map(Self::format_call_arg)
2242            .collect::<Vec<_>>()
2243            .join(", ")
2244    }
2245
2246    /// Format a pattern for printing.
2247    fn format_pattern(&self, pat: &RirPattern) -> String {
2248        match pat {
2249            RirPattern::Wildcard(_) => "_".to_string(),
2250            RirPattern::Int(n, _) => n.to_string(),
2251            RirPattern::Bool(b, _) => b.to_string(),
2252            RirPattern::Path {
2253                module,
2254                type_name,
2255                variant,
2256                ..
2257            } => {
2258                let prefix = if let Some(module_ref) = module {
2259                    format!("%{}..", module_ref.as_u32())
2260                } else {
2261                    String::new()
2262                };
2263                format!(
2264                    "{}{}::{}",
2265                    prefix,
2266                    self.interner.resolve(type_name),
2267                    self.interner.resolve(variant)
2268                )
2269            }
2270            RirPattern::DataVariant {
2271                module,
2272                type_name,
2273                variant,
2274                bindings,
2275                ..
2276            } => {
2277                let prefix = if let Some(module_ref) = module {
2278                    format!("%{}..", module_ref.as_u32())
2279                } else {
2280                    String::new()
2281                };
2282                let binding_strs: Vec<String> = bindings
2283                    .iter()
2284                    .map(|b| {
2285                        if b.is_wildcard {
2286                            "_".to_string()
2287                        } else {
2288                            let name = b
2289                                .name
2290                                .map(|s| self.interner.resolve(&s).to_string())
2291                                .unwrap_or_else(|| "_".to_string());
2292                            if b.is_mut {
2293                                format!("mut {}", name)
2294                            } else {
2295                                name
2296                            }
2297                        }
2298                    })
2299                    .collect();
2300                format!(
2301                    "{}{}::{}({})",
2302                    prefix,
2303                    self.interner.resolve(type_name),
2304                    self.interner.resolve(variant),
2305                    binding_strs.join(", ")
2306                )
2307            }
2308            RirPattern::StructVariant {
2309                module,
2310                type_name,
2311                variant,
2312                field_bindings,
2313                ..
2314            } => {
2315                let prefix = if let Some(module_ref) = module {
2316                    format!("%{}..", module_ref.as_u32())
2317                } else {
2318                    String::new()
2319                };
2320                let field_strs: Vec<String> = field_bindings
2321                    .iter()
2322                    .map(|fb| {
2323                        let field = self.interner.resolve(&fb.field_name);
2324                        if fb.binding.is_wildcard {
2325                            format!("{}: _", field)
2326                        } else {
2327                            let name = fb
2328                                .binding
2329                                .name
2330                                .map(|s| self.interner.resolve(&s).to_string())
2331                                .unwrap_or_else(|| "_".to_string());
2332                            if fb.binding.is_mut {
2333                                format!("{}: mut {}", field, name)
2334                            } else if name == field {
2335                                field.to_string()
2336                            } else {
2337                                format!("{}: {}", field, name)
2338                            }
2339                        }
2340                    })
2341                    .collect();
2342                format!(
2343                    "{}{}::{} {{ {} }}",
2344                    prefix,
2345                    self.interner.resolve(type_name),
2346                    self.interner.resolve(variant),
2347                    field_strs.join(", ")
2348                )
2349            }
2350        }
2351    }
2352
2353    /// Format the RIR as a string.
2354    pub fn render(&self) -> String {
2355        use std::fmt::Write;
2356
2357        let mut out = String::new();
2358        for (inst_ref, inst) in self.rir.iter() {
2359            write!(out, "{} = ", inst_ref).unwrap();
2360            match &inst.data {
2361                // Constants
2362                InstData::IntConst(v) => writeln!(out, "const {}", v).unwrap(),
2363                InstData::FloatConst(bits) => {
2364                    writeln!(out, "const {}", f64::from_bits(*bits)).unwrap()
2365                }
2366                InstData::BoolConst(v) => writeln!(out, "const {}", v).unwrap(),
2367                InstData::StringConst(s) => {
2368                    writeln!(out, "const {:?}", self.interner.resolve(s)).unwrap()
2369                }
2370                InstData::UnitConst => writeln!(out, "const ()").unwrap(),
2371
2372                // Binary operations
2373                InstData::Add { lhs, rhs } => writeln!(out, "add {}, {}", lhs, rhs).unwrap(),
2374                InstData::Sub { lhs, rhs } => writeln!(out, "sub {}, {}", lhs, rhs).unwrap(),
2375                InstData::Mul { lhs, rhs } => writeln!(out, "mul {}, {}", lhs, rhs).unwrap(),
2376                InstData::Div { lhs, rhs } => writeln!(out, "div {}, {}", lhs, rhs).unwrap(),
2377                InstData::Mod { lhs, rhs } => writeln!(out, "mod {}, {}", lhs, rhs).unwrap(),
2378                InstData::Eq { lhs, rhs } => writeln!(out, "eq {}, {}", lhs, rhs).unwrap(),
2379                InstData::Ne { lhs, rhs } => writeln!(out, "ne {}, {}", lhs, rhs).unwrap(),
2380                InstData::Lt { lhs, rhs } => writeln!(out, "lt {}, {}", lhs, rhs).unwrap(),
2381                InstData::Gt { lhs, rhs } => writeln!(out, "gt {}, {}", lhs, rhs).unwrap(),
2382                InstData::Le { lhs, rhs } => writeln!(out, "le {}, {}", lhs, rhs).unwrap(),
2383                InstData::Ge { lhs, rhs } => writeln!(out, "ge {}, {}", lhs, rhs).unwrap(),
2384                InstData::And { lhs, rhs } => writeln!(out, "and {}, {}", lhs, rhs).unwrap(),
2385                InstData::Or { lhs, rhs } => writeln!(out, "or {}, {}", lhs, rhs).unwrap(),
2386                InstData::BitAnd { lhs, rhs } => writeln!(out, "bit_and {}, {}", lhs, rhs).unwrap(),
2387                InstData::BitOr { lhs, rhs } => writeln!(out, "bit_or {}, {}", lhs, rhs).unwrap(),
2388                InstData::BitXor { lhs, rhs } => writeln!(out, "bit_xor {}, {}", lhs, rhs).unwrap(),
2389                InstData::Shl { lhs, rhs } => writeln!(out, "shl {}, {}", lhs, rhs).unwrap(),
2390                InstData::Shr { lhs, rhs } => writeln!(out, "shr {}, {}", lhs, rhs).unwrap(),
2391
2392                // Unary operations
2393                InstData::Neg { operand } => writeln!(out, "neg {}", operand).unwrap(),
2394                InstData::Not { operand } => writeln!(out, "not {}", operand).unwrap(),
2395                InstData::BitNot { operand } => writeln!(out, "bit_not {}", operand).unwrap(),
2396
2397                // Control flow
2398                InstData::Branch {
2399                    cond,
2400                    then_block,
2401                    else_block,
2402                } => {
2403                    if let Some(else_b) = else_block {
2404                        writeln!(out, "branch {}, {}, {}", cond, then_block, else_b).unwrap();
2405                    } else {
2406                        writeln!(out, "branch {}, {}", cond, then_block).unwrap();
2407                    }
2408                }
2409                InstData::Loop { cond, body } => writeln!(out, "loop {}, {}", cond, body).unwrap(),
2410                InstData::For {
2411                    binding,
2412                    is_mut,
2413                    iterable,
2414                    body,
2415                } => {
2416                    let mut_str = if *is_mut { "mut " } else { "" };
2417                    writeln!(
2418                        out,
2419                        "for {}{} in {}, {}",
2420                        mut_str,
2421                        self.interner.resolve(binding),
2422                        iterable,
2423                        body
2424                    )
2425                    .unwrap()
2426                }
2427                InstData::InfiniteLoop { body } => writeln!(out, "infinite_loop {}", body).unwrap(),
2428                InstData::Match {
2429                    scrutinee,
2430                    arms_start,
2431                    arms_len,
2432                } => {
2433                    let arms = self.rir.get_match_arms(*arms_start, *arms_len);
2434                    let arms_str: Vec<String> = arms
2435                        .iter()
2436                        .map(|(pat, body)| format!("{} => {}", self.format_pattern(pat), body))
2437                        .collect();
2438                    writeln!(out, "match {} {{ {} }}", scrutinee, arms_str.join(", ")).unwrap();
2439                }
2440                InstData::Break => writeln!(out, "break").unwrap(),
2441                InstData::Continue => writeln!(out, "continue").unwrap(),
2442
2443                // Functions
2444                InstData::FnDecl {
2445                    directives_start: _,
2446                    directives_len: _,
2447                    is_pub,
2448                    is_unchecked,
2449                    name,
2450                    params_start,
2451                    params_len,
2452                    return_type,
2453                    body,
2454                    has_self,
2455                } => {
2456                    let pub_str = if *is_pub { "pub " } else { "" };
2457                    let unchecked_str = if *is_unchecked { "unchecked " } else { "" };
2458                    let name_str = self.interner.resolve(name);
2459                    let ret_str = self.interner.resolve(return_type);
2460                    let self_str = if *has_self { "self, " } else { "" };
2461                    let params = self.rir.get_params(*params_start, *params_len);
2462                    let params_str: Vec<String> = params
2463                        .iter()
2464                        .map(|p| {
2465                            let mode_prefix = match p.mode {
2466                                RirParamMode::Inout => "inout ",
2467                                RirParamMode::Borrow => "borrow ",
2468                                RirParamMode::Comptime => "comptime ",
2469                                RirParamMode::Normal => "",
2470                            };
2471                            format!(
2472                                "{}{}: {}",
2473                                mode_prefix,
2474                                self.interner.resolve(&p.name),
2475                                self.interner.resolve(&p.ty)
2476                            )
2477                        })
2478                        .collect();
2479                    writeln!(
2480                        out,
2481                        "{}{}fn {}({}{}) -> {} {{",
2482                        pub_str,
2483                        unchecked_str,
2484                        name_str,
2485                        self_str,
2486                        params_str.join(", "),
2487                        ret_str
2488                    )
2489                    .unwrap();
2490                    writeln!(out, "    {}", body).unwrap();
2491                    writeln!(out, "}}").unwrap();
2492                }
2493                InstData::ConstDecl {
2494                    directives_start: _,
2495                    directives_len: _,
2496                    is_pub,
2497                    name,
2498                    ty,
2499                    init,
2500                } => {
2501                    let pub_str = if *is_pub { "pub " } else { "" };
2502                    let name_str = self.interner.resolve(name);
2503                    let ty_str = ty
2504                        .map(|t| format!(": {}", self.interner.resolve(&t)))
2505                        .unwrap_or_default();
2506                    writeln!(out, "{}const {}{} = {}", pub_str, name_str, ty_str, init).unwrap();
2507                }
2508                InstData::Ret(inner) => {
2509                    if let Some(inner) = inner {
2510                        writeln!(out, "ret {}", inner).unwrap();
2511                    } else {
2512                        writeln!(out, "ret").unwrap();
2513                    }
2514                }
2515                InstData::Call {
2516                    name,
2517                    args_start,
2518                    args_len,
2519                } => {
2520                    let name_str = self.interner.resolve(name);
2521                    let args = self.rir.get_call_args(*args_start, *args_len);
2522                    writeln!(out, "call {}({})", name_str, Self::format_call_args(&args)).unwrap();
2523                }
2524                InstData::Intrinsic {
2525                    name,
2526                    args_start,
2527                    args_len,
2528                } => {
2529                    let name_str = self.interner.resolve(name);
2530                    let args = self.rir.get_inst_refs(*args_start, *args_len);
2531                    let args_str: Vec<String> = args.iter().map(|a| format!("{}", a)).collect();
2532                    writeln!(out, "intrinsic @{}({})", name_str, args_str.join(", ")).unwrap();
2533                }
2534                InstData::TypeIntrinsic { name, type_arg } => {
2535                    let name_str = self.interner.resolve(name);
2536                    let type_str = self.interner.resolve(type_arg);
2537                    writeln!(out, "type_intrinsic @{}({})", name_str, type_str).unwrap();
2538                }
2539                InstData::ParamRef { index, name } => {
2540                    writeln!(out, "param {} ({})", index, self.interner.resolve(name)).unwrap();
2541                }
2542                InstData::Block { extra_start, len } => {
2543                    writeln!(out, "block({}, {})", extra_start, len).unwrap();
2544                }
2545
2546                // Variables
2547                InstData::Alloc {
2548                    directives_start: _,
2549                    directives_len: _,
2550                    name,
2551                    is_mut,
2552                    ty,
2553                    init,
2554                } => {
2555                    let name_str = name
2556                        .map(|n| self.interner.resolve(&n).to_string())
2557                        .unwrap_or_else(|| "_".to_string());
2558                    let mut_str = if *is_mut { "mut " } else { "" };
2559                    let ty_str = ty
2560                        .map(|t| format!(": {}", self.interner.resolve(&t)))
2561                        .unwrap_or_default();
2562                    writeln!(out, "alloc {}{}{}= {}", mut_str, name_str, ty_str, init).unwrap();
2563                }
2564                InstData::StructDestructure {
2565                    type_name,
2566                    fields_start,
2567                    fields_len,
2568                    init,
2569                } => {
2570                    let type_str = self.interner.resolve(type_name);
2571                    let fields = self.rir.get_destructure_fields(*fields_start, *fields_len);
2572                    let field_strs: Vec<String> = fields
2573                        .iter()
2574                        .map(|f| {
2575                            let name = self.interner.resolve(&f.field_name);
2576                            if f.is_wildcard {
2577                                format!("{}: _", name)
2578                            } else if let Some(binding) = f.binding_name {
2579                                let binding_str = self.interner.resolve(&binding);
2580                                format!("{}: {}", name, binding_str)
2581                            } else {
2582                                name.to_string()
2583                            }
2584                        })
2585                        .collect();
2586                    writeln!(
2587                        out,
2588                        "destructure {} {{ {} }} = {}",
2589                        type_str,
2590                        field_strs.join(", "),
2591                        init
2592                    )
2593                    .unwrap();
2594                }
2595                InstData::VarRef { name } => {
2596                    writeln!(out, "var_ref {}", self.interner.resolve(name)).unwrap();
2597                }
2598                InstData::Assign { name, value } => {
2599                    writeln!(out, "assign {} = {}", self.interner.resolve(name), value).unwrap();
2600                }
2601
2602                // Structs
2603                InstData::StructDecl {
2604                    directives_start,
2605                    directives_len,
2606                    is_pub,
2607                    is_linear,
2608                    name,
2609                    fields_start,
2610                    fields_len,
2611                    methods_start,
2612                    methods_len,
2613                } => {
2614                    let pub_str = if *is_pub { "pub " } else { "" };
2615                    let name_str = self.interner.resolve(name);
2616                    let fields = self.rir.get_field_decls(*fields_start, *fields_len);
2617                    let fields_str: Vec<String> = fields
2618                        .iter()
2619                        .map(|(fname, ftype)| {
2620                            format!(
2621                                "{}: {}",
2622                                self.interner.resolve(fname),
2623                                self.interner.resolve(ftype)
2624                            )
2625                        })
2626                        .collect();
2627                    let directives = self.rir.get_directives(*directives_start, *directives_len);
2628                    let linear_str = if *is_linear { "linear " } else { "" };
2629                    let directives_str = if directives.is_empty() {
2630                        String::new()
2631                    } else {
2632                        let dir_names: Vec<String> = directives
2633                            .iter()
2634                            .map(|d| format!("@{}", self.interner.resolve(&d.name)))
2635                            .collect();
2636                        format!("{} ", dir_names.join(" "))
2637                    };
2638                    let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
2639                    let methods_str = if methods.is_empty() {
2640                        String::new()
2641                    } else {
2642                        let method_refs: Vec<String> =
2643                            methods.iter().map(|m| format!("{}", m)).collect();
2644                        format!(" methods: [{}]", method_refs.join(", "))
2645                    };
2646                    writeln!(
2647                        out,
2648                        "{}{}{}struct {} {{ {} }}{}",
2649                        directives_str,
2650                        pub_str,
2651                        linear_str,
2652                        name_str,
2653                        fields_str.join(", "),
2654                        methods_str
2655                    )
2656                    .unwrap();
2657                }
2658                InstData::StructInit {
2659                    module,
2660                    type_name,
2661                    fields_start,
2662                    fields_len,
2663                } => {
2664                    let module_str = module.map(|m| format!("{}.", m)).unwrap_or_default();
2665                    let type_str = self.interner.resolve(type_name);
2666                    let fields = self.rir.get_field_inits(*fields_start, *fields_len);
2667                    let fields_str: Vec<String> = fields
2668                        .iter()
2669                        .map(|(fname, value)| {
2670                            format!("{}: {}", self.interner.resolve(fname), value)
2671                        })
2672                        .collect();
2673                    writeln!(
2674                        out,
2675                        "struct_init {}{} {{ {} }}",
2676                        module_str,
2677                        type_str,
2678                        fields_str.join(", ")
2679                    )
2680                    .unwrap();
2681                }
2682                InstData::FieldGet { base, field } => {
2683                    writeln!(out, "field_get {}.{}", base, self.interner.resolve(field)).unwrap();
2684                }
2685                InstData::FieldSet { base, field, value } => {
2686                    writeln!(
2687                        out,
2688                        "field_set {}.{} = {}",
2689                        base,
2690                        self.interner.resolve(field),
2691                        value
2692                    )
2693                    .unwrap();
2694                }
2695
2696                // Enums
2697                InstData::EnumDecl {
2698                    is_pub,
2699                    name,
2700                    variants_start,
2701                    variants_len,
2702                } => {
2703                    let pub_str = if *is_pub { "pub " } else { "" };
2704                    let name_str = self.interner.resolve(name);
2705                    let variants = self
2706                        .rir
2707                        .get_enum_variant_decls(*variants_start, *variants_len);
2708                    let variants_str: Vec<String> = variants
2709                        .iter()
2710                        .map(|(v, field_types, field_names)| {
2711                            let vname = self.interner.resolve(v).to_string();
2712                            if field_types.is_empty() {
2713                                vname
2714                            } else if field_names.is_empty() {
2715                                // Tuple variant
2716                                let field_strs: Vec<&str> = field_types
2717                                    .iter()
2718                                    .map(|f| self.interner.resolve(f))
2719                                    .collect();
2720                                format!("{}({})", vname, field_strs.join(", "))
2721                            } else {
2722                                // Struct variant
2723                                let field_strs: Vec<String> = field_names
2724                                    .iter()
2725                                    .zip(field_types.iter())
2726                                    .map(|(n, t)| {
2727                                        format!(
2728                                            "{}: {}",
2729                                            self.interner.resolve(n),
2730                                            self.interner.resolve(t)
2731                                        )
2732                                    })
2733                                    .collect();
2734                                format!("{} {{ {} }}", vname, field_strs.join(", "))
2735                            }
2736                        })
2737                        .collect();
2738                    writeln!(
2739                        out,
2740                        "{}enum {} {{ {} }}",
2741                        pub_str,
2742                        name_str,
2743                        variants_str.join(", ")
2744                    )
2745                    .unwrap();
2746                }
2747                InstData::EnumVariant {
2748                    module,
2749                    type_name,
2750                    variant,
2751                } => {
2752                    let module_str = module.map(|m| format!("{}.", m)).unwrap_or_default();
2753                    writeln!(
2754                        out,
2755                        "enum_variant {}{}::{}",
2756                        module_str,
2757                        self.interner.resolve(type_name),
2758                        self.interner.resolve(variant)
2759                    )
2760                    .unwrap();
2761                }
2762                InstData::EnumStructVariant {
2763                    module,
2764                    type_name,
2765                    variant,
2766                    fields_start,
2767                    fields_len,
2768                } => {
2769                    let module_str = module.map(|m| format!("{}.", m)).unwrap_or_default();
2770                    let fields = self.rir.get_field_inits(*fields_start, *fields_len);
2771                    let field_strs: Vec<String> = fields
2772                        .iter()
2773                        .map(|(name, value)| format!("{}: {}", self.interner.resolve(name), value))
2774                        .collect();
2775                    writeln!(
2776                        out,
2777                        "enum_struct_variant {}{}::{} {{ {} }}",
2778                        module_str,
2779                        self.interner.resolve(type_name),
2780                        self.interner.resolve(variant),
2781                        field_strs.join(", ")
2782                    )
2783                    .unwrap();
2784                }
2785
2786                // Arrays
2787                InstData::ArrayInit {
2788                    elems_start,
2789                    elems_len,
2790                } => {
2791                    let elements = self.rir.get_inst_refs(*elems_start, *elems_len);
2792                    let elems_str: Vec<String> =
2793                        elements.iter().map(|e| format!("{}", e)).collect();
2794                    writeln!(out, "array_init [{}]", elems_str.join(", ")).unwrap();
2795                }
2796                InstData::IndexGet { base, index } => {
2797                    writeln!(out, "index_get {}[{}]", base, index).unwrap();
2798                }
2799                InstData::IndexSet { base, index, value } => {
2800                    writeln!(out, "index_set {}[{}] = {}", base, index, value).unwrap();
2801                }
2802
2803                // Methods
2804                InstData::MethodCall {
2805                    receiver,
2806                    method,
2807                    args_start,
2808                    args_len,
2809                } => {
2810                    let args = self.rir.get_call_args(*args_start, *args_len);
2811                    writeln!(
2812                        out,
2813                        "method_call {}.{}({})",
2814                        receiver,
2815                        self.interner.resolve(method),
2816                        Self::format_call_args(&args)
2817                    )
2818                    .unwrap();
2819                }
2820                InstData::AssocFnCall {
2821                    type_name,
2822                    function,
2823                    args_start,
2824                    args_len,
2825                } => {
2826                    let args = self.rir.get_call_args(*args_start, *args_len);
2827                    writeln!(
2828                        out,
2829                        "assoc_fn_call {}::{}({})",
2830                        self.interner.resolve(type_name),
2831                        self.interner.resolve(function),
2832                        Self::format_call_args(&args)
2833                    )
2834                    .unwrap();
2835                }
2836
2837                // Drop
2838                InstData::DropFnDecl { type_name, body } => {
2839                    writeln!(out, "drop fn {}(self) {{", self.interner.resolve(type_name)).unwrap();
2840                    writeln!(out, "    {}", body).unwrap();
2841                    writeln!(out, "}}").unwrap();
2842                }
2843
2844                // Comptime block
2845                InstData::Comptime { expr } => {
2846                    writeln!(out, "comptime {{ {} }}", expr).unwrap();
2847                }
2848
2849                // Comptime unroll for
2850                InstData::ComptimeUnrollFor {
2851                    binding,
2852                    iterable,
2853                    body,
2854                } => writeln!(
2855                    out,
2856                    "comptime_unroll for {} in {}, {}",
2857                    self.interner.resolve(binding),
2858                    iterable,
2859                    body
2860                )
2861                .unwrap(),
2862
2863                // Checked block
2864                InstData::Checked { expr } => {
2865                    writeln!(out, "checked {{ {} }}", expr).unwrap();
2866                }
2867
2868                // Type constant
2869                InstData::TypeConst { type_name } => {
2870                    let name = self.interner.resolve(type_name);
2871                    writeln!(out, "type {}", name).unwrap();
2872                }
2873
2874                // Anonymous struct type
2875                InstData::AnonStructType {
2876                    fields_start,
2877                    fields_len,
2878                    methods_start,
2879                    methods_len,
2880                } => {
2881                    write!(out, "struct {{ ").unwrap();
2882                    let fields = self.rir.get_field_decls(*fields_start, *fields_len);
2883                    for (i, (name, ty)) in fields.iter().enumerate() {
2884                        if i > 0 {
2885                            write!(out, ", ").unwrap();
2886                        }
2887                        let name_str = self.interner.resolve(name);
2888                        let ty_str = self.interner.resolve(ty);
2889                        write!(out, "{}: {}", name_str, ty_str).unwrap();
2890                    }
2891                    // Print methods if any
2892                    if *methods_len > 0 {
2893                        let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
2894                        let methods_str: Vec<String> =
2895                            methods.iter().map(|m| format!("{}", m)).collect();
2896                        if !fields.is_empty() {
2897                            write!(out, ", ").unwrap();
2898                        }
2899                        write!(out, "methods: [{}]", methods_str.join(", ")).unwrap();
2900                    }
2901                    writeln!(out, " }}").unwrap();
2902                }
2903
2904                // Anonymous enum type
2905                InstData::AnonEnumType {
2906                    variants_start,
2907                    variants_len,
2908                    methods_start,
2909                    methods_len,
2910                } => {
2911                    write!(out, "enum {{ ").unwrap();
2912                    let variants = self
2913                        .rir
2914                        .get_enum_variant_decls(*variants_start, *variants_len);
2915                    let variants_str: Vec<String> = variants
2916                        .iter()
2917                        .map(|(v, field_types, field_names)| {
2918                            let vname = self.interner.resolve(v).to_string();
2919                            if field_types.is_empty() {
2920                                vname
2921                            } else if field_names.is_empty() {
2922                                let field_strs: Vec<&str> = field_types
2923                                    .iter()
2924                                    .map(|f| self.interner.resolve(f))
2925                                    .collect();
2926                                format!("{}({})", vname, field_strs.join(", "))
2927                            } else {
2928                                let field_strs: Vec<String> = field_names
2929                                    .iter()
2930                                    .zip(field_types.iter())
2931                                    .map(|(n, t)| {
2932                                        format!(
2933                                            "{}: {}",
2934                                            self.interner.resolve(n),
2935                                            self.interner.resolve(t)
2936                                        )
2937                                    })
2938                                    .collect();
2939                                format!("{} {{ {} }}", vname, field_strs.join(", "))
2940                            }
2941                        })
2942                        .collect();
2943                    write!(out, "{}", variants_str.join(", ")).unwrap();
2944                    if *methods_len > 0 {
2945                        let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
2946                        let methods_str: Vec<String> =
2947                            methods.iter().map(|m| format!("{}", m)).collect();
2948                        if !variants_str.is_empty() {
2949                            write!(out, ", ").unwrap();
2950                        }
2951                        write!(out, "methods: [{}]", methods_str.join(", ")).unwrap();
2952                    }
2953                    writeln!(out, " }}").unwrap();
2954                }
2955            }
2956        }
2957        out
2958    }
2959}
2960
2961impl fmt::Display for RirPrinter<'_, '_> {
2962    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2963        write!(f, "{}", self.render())
2964    }
2965}
2966
2967#[cfg(test)]
2968mod tests {
2969    use super::*;
2970    use lasso::ThreadedRodeo;
2971
2972    #[test]
2973    fn test_inst_ref_size() {
2974        assert_eq!(std::mem::size_of::<InstRef>(), 4);
2975    }
2976
2977    #[test]
2978    fn test_add_and_get_inst() {
2979        let mut rir = Rir::new();
2980        let inst = Inst {
2981            data: InstData::IntConst(42),
2982            span: Span::new(0, 2),
2983        };
2984        let inst_ref = rir.add_inst(inst);
2985
2986        let retrieved = rir.get(inst_ref);
2987        assert!(matches!(retrieved.data, InstData::IntConst(42)));
2988    }
2989
2990    #[test]
2991    fn test_rir_is_empty() {
2992        let rir = Rir::new();
2993        assert!(rir.is_empty());
2994        assert_eq!(rir.len(), 0);
2995    }
2996
2997    #[test]
2998    fn test_rir_extra_data() {
2999        let mut rir = Rir::new();
3000        let data = [1, 2, 3, 4, 5];
3001        let start = rir.add_extra(&data);
3002        assert_eq!(start, 0);
3003
3004        let retrieved = rir.get_extra(start, 5);
3005        assert_eq!(retrieved, &data);
3006
3007        // Add more extra data
3008        let data2 = [10, 20];
3009        let start2 = rir.add_extra(&data2);
3010        assert_eq!(start2, 5);
3011    }
3012
3013    #[test]
3014    fn test_rir_iter() {
3015        let mut rir = Rir::new();
3016        rir.add_inst(Inst {
3017            data: InstData::IntConst(1),
3018            span: Span::new(0, 1),
3019        });
3020        rir.add_inst(Inst {
3021            data: InstData::IntConst(2),
3022            span: Span::new(2, 3),
3023        });
3024
3025        let items: Vec<_> = rir.iter().collect();
3026        assert_eq!(items.len(), 2);
3027        assert_eq!(items[0].0.as_u32(), 0);
3028        assert_eq!(items[1].0.as_u32(), 1);
3029    }
3030
3031    #[test]
3032    fn test_inst_ref_display() {
3033        let inst_ref = InstRef::from_raw(42);
3034        assert_eq!(format!("{}", inst_ref), "%42");
3035    }
3036
3037    // RirPattern tests
3038    #[test]
3039    fn test_rir_pattern_wildcard_span() {
3040        let span = Span::new(10, 11);
3041        let pattern = RirPattern::Wildcard(span);
3042        assert_eq!(pattern.span(), span);
3043    }
3044
3045    #[test]
3046    fn test_rir_pattern_int_span() {
3047        let span = Span::new(20, 22);
3048        let pattern = RirPattern::Int(42, span);
3049        assert_eq!(pattern.span(), span);
3050
3051        // Test negative int
3052        let pattern_neg = RirPattern::Int(-100, span);
3053        assert_eq!(pattern_neg.span(), span);
3054    }
3055
3056    #[test]
3057    fn test_rir_pattern_bool_span() {
3058        let span = Span::new(30, 34);
3059        let pattern = RirPattern::Bool(true, span);
3060        assert_eq!(pattern.span(), span);
3061
3062        let pattern_false = RirPattern::Bool(false, span);
3063        assert_eq!(pattern_false.span(), span);
3064    }
3065
3066    #[test]
3067    fn test_rir_pattern_path_span() {
3068        let span = Span::new(40, 50);
3069        let interner = ThreadedRodeo::new();
3070        let type_name = interner.get_or_intern("Color");
3071        let variant = interner.get_or_intern("Red");
3072
3073        let pattern = RirPattern::Path {
3074            module: None,
3075            type_name,
3076            variant,
3077            span,
3078        };
3079        assert_eq!(pattern.span(), span);
3080    }
3081
3082    // RirCallArg tests
3083    #[test]
3084    fn test_rir_call_arg_is_inout() {
3085        let arg_normal = RirCallArg {
3086            value: InstRef::from_raw(0),
3087            mode: RirArgMode::Normal,
3088        };
3089        assert!(!arg_normal.is_inout());
3090        assert!(!arg_normal.is_borrow());
3091
3092        let arg_inout = RirCallArg {
3093            value: InstRef::from_raw(0),
3094            mode: RirArgMode::Inout,
3095        };
3096        assert!(arg_inout.is_inout());
3097        assert!(!arg_inout.is_borrow());
3098
3099        let arg_borrow = RirCallArg {
3100            value: InstRef::from_raw(0),
3101            mode: RirArgMode::Borrow,
3102        };
3103        assert!(!arg_borrow.is_inout());
3104        assert!(arg_borrow.is_borrow());
3105    }
3106
3107    // RirPrinter tests
3108    fn create_printer_test_rir() -> (Rir, ThreadedRodeo) {
3109        let rir = Rir::new();
3110        let interner = ThreadedRodeo::new();
3111        (rir, interner)
3112    }
3113
3114    #[test]
3115    fn test_printer_int_const() {
3116        let (mut rir, interner) = create_printer_test_rir();
3117        rir.add_inst(Inst {
3118            data: InstData::IntConst(42),
3119            span: Span::new(0, 2),
3120        });
3121
3122        let printer = RirPrinter::new(&rir, &interner);
3123        let output = printer.to_string();
3124        assert!(output.contains("%0 = const 42"));
3125    }
3126
3127    #[test]
3128    fn test_printer_bool_const() {
3129        let (mut rir, interner) = create_printer_test_rir();
3130        rir.add_inst(Inst {
3131            data: InstData::BoolConst(true),
3132            span: Span::new(0, 4),
3133        });
3134        rir.add_inst(Inst {
3135            data: InstData::BoolConst(false),
3136            span: Span::new(0, 5),
3137        });
3138
3139        let printer = RirPrinter::new(&rir, &interner);
3140        let output = printer.to_string();
3141        assert!(output.contains("%0 = const true"));
3142        assert!(output.contains("%1 = const false"));
3143    }
3144
3145    #[test]
3146    fn test_printer_string_const() {
3147        let (mut rir, interner) = create_printer_test_rir();
3148        let hello = interner.get_or_intern("hello world");
3149        rir.add_inst(Inst {
3150            data: InstData::StringConst(hello),
3151            span: Span::new(0, 13),
3152        });
3153
3154        let printer = RirPrinter::new(&rir, &interner);
3155        let output = printer.to_string();
3156        assert!(output.contains("%0 = const \"hello world\""));
3157    }
3158
3159    #[test]
3160    fn test_printer_unit_const() {
3161        let (mut rir, interner) = create_printer_test_rir();
3162        rir.add_inst(Inst {
3163            data: InstData::UnitConst,
3164            span: Span::new(0, 2),
3165        });
3166
3167        let printer = RirPrinter::new(&rir, &interner);
3168        let output = printer.to_string();
3169        assert!(output.contains("%0 = const ()"));
3170    }
3171
3172    #[test]
3173    fn test_printer_binary_ops() {
3174        let (mut rir, interner) = create_printer_test_rir();
3175        let lhs = rir.add_inst(Inst {
3176            data: InstData::IntConst(1),
3177            span: Span::new(0, 1),
3178        });
3179        let rhs = rir.add_inst(Inst {
3180            data: InstData::IntConst(2),
3181            span: Span::new(2, 3),
3182        });
3183
3184        // Test all binary operations
3185        let ops = vec![
3186            (InstData::Add { lhs, rhs }, "add"),
3187            (InstData::Sub { lhs, rhs }, "sub"),
3188            (InstData::Mul { lhs, rhs }, "mul"),
3189            (InstData::Div { lhs, rhs }, "div"),
3190            (InstData::Mod { lhs, rhs }, "mod"),
3191            (InstData::Eq { lhs, rhs }, "eq"),
3192            (InstData::Ne { lhs, rhs }, "ne"),
3193            (InstData::Lt { lhs, rhs }, "lt"),
3194            (InstData::Gt { lhs, rhs }, "gt"),
3195            (InstData::Le { lhs, rhs }, "le"),
3196            (InstData::Ge { lhs, rhs }, "ge"),
3197            (InstData::And { lhs, rhs }, "and"),
3198            (InstData::Or { lhs, rhs }, "or"),
3199            (InstData::BitAnd { lhs, rhs }, "bit_and"),
3200            (InstData::BitOr { lhs, rhs }, "bit_or"),
3201            (InstData::BitXor { lhs, rhs }, "bit_xor"),
3202            (InstData::Shl { lhs, rhs }, "shl"),
3203            (InstData::Shr { lhs, rhs }, "shr"),
3204        ];
3205
3206        for (_data, op_name) in ops {
3207            let mut test_rir = Rir::new();
3208            let lhs = test_rir.add_inst(Inst {
3209                data: InstData::IntConst(1),
3210                span: Span::new(0, 1),
3211            });
3212            let rhs = test_rir.add_inst(Inst {
3213                data: InstData::IntConst(2),
3214                span: Span::new(2, 3),
3215            });
3216            // Recreate the data with new refs
3217            let data = match op_name {
3218                "add" => InstData::Add { lhs, rhs },
3219                "sub" => InstData::Sub { lhs, rhs },
3220                "mul" => InstData::Mul { lhs, rhs },
3221                "div" => InstData::Div { lhs, rhs },
3222                "mod" => InstData::Mod { lhs, rhs },
3223                "eq" => InstData::Eq { lhs, rhs },
3224                "ne" => InstData::Ne { lhs, rhs },
3225                "lt" => InstData::Lt { lhs, rhs },
3226                "gt" => InstData::Gt { lhs, rhs },
3227                "le" => InstData::Le { lhs, rhs },
3228                "ge" => InstData::Ge { lhs, rhs },
3229                "and" => InstData::And { lhs, rhs },
3230                "or" => InstData::Or { lhs, rhs },
3231                "bit_and" => InstData::BitAnd { lhs, rhs },
3232                "bit_or" => InstData::BitOr { lhs, rhs },
3233                "bit_xor" => InstData::BitXor { lhs, rhs },
3234                "shl" => InstData::Shl { lhs, rhs },
3235                "shr" => InstData::Shr { lhs, rhs },
3236                _ => unreachable!(),
3237            };
3238            test_rir.add_inst(Inst {
3239                data,
3240                span: Span::new(0, 5),
3241            });
3242
3243            let printer = RirPrinter::new(&test_rir, &interner);
3244            let output = printer.to_string();
3245            let expected = format!("%2 = {} %0, %1", op_name);
3246            assert!(
3247                output.contains(&expected),
3248                "Expected '{}' in output:\n{}",
3249                expected,
3250                output
3251            );
3252        }
3253    }
3254
3255    #[test]
3256    fn test_printer_unary_ops() {
3257        let (mut rir, interner) = create_printer_test_rir();
3258        let operand = rir.add_inst(Inst {
3259            data: InstData::IntConst(42),
3260            span: Span::new(0, 2),
3261        });
3262
3263        rir.add_inst(Inst {
3264            data: InstData::Neg { operand },
3265            span: Span::new(0, 3),
3266        });
3267        rir.add_inst(Inst {
3268            data: InstData::Not { operand },
3269            span: Span::new(0, 3),
3270        });
3271        rir.add_inst(Inst {
3272            data: InstData::BitNot { operand },
3273            span: Span::new(0, 3),
3274        });
3275
3276        let printer = RirPrinter::new(&rir, &interner);
3277        let output = printer.to_string();
3278        assert!(output.contains("neg %0"));
3279        assert!(output.contains("not %0"));
3280        assert!(output.contains("bit_not %0"));
3281    }
3282
3283    #[test]
3284    fn test_printer_branch() {
3285        let (mut rir, interner) = create_printer_test_rir();
3286        let cond = rir.add_inst(Inst {
3287            data: InstData::BoolConst(true),
3288            span: Span::new(0, 4),
3289        });
3290        let then_block = rir.add_inst(Inst {
3291            data: InstData::IntConst(1),
3292            span: Span::new(0, 1),
3293        });
3294        let else_block = rir.add_inst(Inst {
3295            data: InstData::IntConst(0),
3296            span: Span::new(0, 1),
3297        });
3298
3299        // With else block
3300        rir.add_inst(Inst {
3301            data: InstData::Branch {
3302                cond,
3303                then_block,
3304                else_block: Some(else_block),
3305            },
3306            span: Span::new(0, 20),
3307        });
3308
3309        let printer = RirPrinter::new(&rir, &interner);
3310        let output = printer.to_string();
3311        assert!(output.contains("branch %0, %1, %2"));
3312    }
3313
3314    #[test]
3315    fn test_printer_branch_no_else() {
3316        let (mut rir, interner) = create_printer_test_rir();
3317        let cond = rir.add_inst(Inst {
3318            data: InstData::BoolConst(true),
3319            span: Span::new(0, 4),
3320        });
3321        let then_block = rir.add_inst(Inst {
3322            data: InstData::IntConst(1),
3323            span: Span::new(0, 1),
3324        });
3325
3326        rir.add_inst(Inst {
3327            data: InstData::Branch {
3328                cond,
3329                then_block,
3330                else_block: None,
3331            },
3332            span: Span::new(0, 15),
3333        });
3334
3335        let printer = RirPrinter::new(&rir, &interner);
3336        let output = printer.to_string();
3337        // Should not have the third argument
3338        assert!(output.contains("branch %0, %1\n"));
3339    }
3340
3341    #[test]
3342    fn test_printer_loop() {
3343        let (mut rir, interner) = create_printer_test_rir();
3344        let cond = rir.add_inst(Inst {
3345            data: InstData::BoolConst(true),
3346            span: Span::new(0, 4),
3347        });
3348        let body = rir.add_inst(Inst {
3349            data: InstData::IntConst(0),
3350            span: Span::new(0, 1),
3351        });
3352
3353        rir.add_inst(Inst {
3354            data: InstData::Loop { cond, body },
3355            span: Span::new(0, 20),
3356        });
3357
3358        let printer = RirPrinter::new(&rir, &interner);
3359        let output = printer.to_string();
3360        assert!(output.contains("loop %0, %1"));
3361    }
3362
3363    #[test]
3364    fn test_printer_infinite_loop() {
3365        let (mut rir, interner) = create_printer_test_rir();
3366        let body = rir.add_inst(Inst {
3367            data: InstData::IntConst(0),
3368            span: Span::new(0, 1),
3369        });
3370
3371        rir.add_inst(Inst {
3372            data: InstData::InfiniteLoop { body },
3373            span: Span::new(0, 15),
3374        });
3375
3376        let printer = RirPrinter::new(&rir, &interner);
3377        let output = printer.to_string();
3378        assert!(output.contains("infinite_loop %0"));
3379    }
3380
3381    #[test]
3382    fn test_printer_break_continue() {
3383        let (mut rir, interner) = create_printer_test_rir();
3384        rir.add_inst(Inst {
3385            data: InstData::Break,
3386            span: Span::new(0, 5),
3387        });
3388        rir.add_inst(Inst {
3389            data: InstData::Continue,
3390            span: Span::new(0, 8),
3391        });
3392
3393        let printer = RirPrinter::new(&rir, &interner);
3394        let output = printer.to_string();
3395        assert!(output.contains("break\n"));
3396        assert!(output.contains("continue\n"));
3397    }
3398
3399    #[test]
3400    fn test_printer_ret() {
3401        let (mut rir, interner) = create_printer_test_rir();
3402        let value = rir.add_inst(Inst {
3403            data: InstData::IntConst(42),
3404            span: Span::new(0, 2),
3405        });
3406
3407        // Return with value
3408        rir.add_inst(Inst {
3409            data: InstData::Ret(Some(value)),
3410            span: Span::new(0, 10),
3411        });
3412        // Return without value
3413        rir.add_inst(Inst {
3414            data: InstData::Ret(None),
3415            span: Span::new(0, 6),
3416        });
3417
3418        let printer = RirPrinter::new(&rir, &interner);
3419        let output = printer.to_string();
3420        assert!(output.contains("ret %0"));
3421        assert!(output.contains("%2 = ret\n"));
3422    }
3423
3424    #[test]
3425    fn test_printer_fn_decl() {
3426        let (mut rir, interner) = create_printer_test_rir();
3427        let body = rir.add_inst(Inst {
3428            data: InstData::IntConst(42),
3429            span: Span::new(0, 2),
3430        });
3431
3432        let name = interner.get_or_intern("main");
3433        let return_type = interner.get_or_intern("i32");
3434        let param_name = interner.get_or_intern("x");
3435        let param_type = interner.get_or_intern("i32");
3436
3437        let (directives_start, directives_len) = rir.add_directives(&[]);
3438        let (params_start, params_len) = rir.add_params(&[RirParam {
3439            name: param_name,
3440            ty: param_type,
3441            mode: RirParamMode::Normal,
3442            is_comptime: false,
3443        }]);
3444
3445        rir.add_inst(Inst {
3446            data: InstData::FnDecl {
3447                directives_start,
3448                directives_len,
3449                is_pub: false,
3450                is_unchecked: false,
3451                name,
3452                params_start,
3453                params_len,
3454                return_type,
3455                body,
3456                has_self: false,
3457            },
3458            span: Span::new(0, 30),
3459        });
3460
3461        let printer = RirPrinter::new(&rir, &interner);
3462        let output = printer.to_string();
3463        assert!(output.contains("fn main(x: i32) -> i32"));
3464    }
3465
3466    #[test]
3467    fn test_printer_fn_decl_with_self() {
3468        let (mut rir, interner) = create_printer_test_rir();
3469        let body = rir.add_inst(Inst {
3470            data: InstData::IntConst(0),
3471            span: Span::new(0, 1),
3472        });
3473
3474        let name = interner.get_or_intern("get_x");
3475        let return_type = interner.get_or_intern("i32");
3476
3477        let (directives_start, directives_len) = rir.add_directives(&[]);
3478        let (params_start, params_len) = rir.add_params(&[]);
3479
3480        rir.add_inst(Inst {
3481            data: InstData::FnDecl {
3482                directives_start,
3483                directives_len,
3484                is_pub: false,
3485                is_unchecked: false,
3486                name,
3487                params_start,
3488                params_len,
3489                return_type,
3490                body,
3491                has_self: true,
3492            },
3493            span: Span::new(0, 30),
3494        });
3495
3496        let printer = RirPrinter::new(&rir, &interner);
3497        let output = printer.to_string();
3498        assert!(output.contains("fn get_x(self, ) -> i32"));
3499    }
3500
3501    #[test]
3502    fn test_printer_fn_decl_param_modes() {
3503        let (mut rir, interner) = create_printer_test_rir();
3504        let body = rir.add_inst(Inst {
3505            data: InstData::UnitConst,
3506            span: Span::new(0, 2),
3507        });
3508
3509        let name = interner.get_or_intern("modify");
3510        let return_type = interner.get_or_intern("()");
3511        let param1_name = interner.get_or_intern("a");
3512        let param1_type = interner.get_or_intern("i32");
3513        let param2_name = interner.get_or_intern("b");
3514        let param2_type = interner.get_or_intern("i32");
3515        let param3_name = interner.get_or_intern("c");
3516        let param3_type = interner.get_or_intern("i32");
3517
3518        let (directives_start, directives_len) = rir.add_directives(&[]);
3519        let (params_start, params_len) = rir.add_params(&[
3520            RirParam {
3521                name: param1_name,
3522                ty: param1_type,
3523                mode: RirParamMode::Normal,
3524                is_comptime: false,
3525            },
3526            RirParam {
3527                name: param2_name,
3528                ty: param2_type,
3529                mode: RirParamMode::Inout,
3530                is_comptime: false,
3531            },
3532            RirParam {
3533                name: param3_name,
3534                ty: param3_type,
3535                mode: RirParamMode::Borrow,
3536                is_comptime: false,
3537            },
3538        ]);
3539
3540        rir.add_inst(Inst {
3541            data: InstData::FnDecl {
3542                directives_start,
3543                directives_len,
3544                is_pub: false,
3545                is_unchecked: false,
3546                name,
3547                params_start,
3548                params_len,
3549                return_type,
3550                body,
3551                has_self: false,
3552            },
3553            span: Span::new(0, 50),
3554        });
3555
3556        let printer = RirPrinter::new(&rir, &interner);
3557        let output = printer.to_string();
3558        assert!(output.contains("a: i32"));
3559        assert!(output.contains("inout b: i32"));
3560        assert!(output.contains("borrow c: i32"));
3561    }
3562
3563    #[test]
3564    fn test_printer_call() {
3565        let (mut rir, interner) = create_printer_test_rir();
3566        let arg = rir.add_inst(Inst {
3567            data: InstData::IntConst(10),
3568            span: Span::new(0, 2),
3569        });
3570
3571        let name = interner.get_or_intern("foo");
3572
3573        let (args_start, args_len) = rir.add_call_args(&[RirCallArg {
3574            value: arg,
3575            mode: RirArgMode::Normal,
3576        }]);
3577
3578        rir.add_inst(Inst {
3579            data: InstData::Call {
3580                name,
3581                args_start,
3582                args_len,
3583            },
3584            span: Span::new(0, 8),
3585        });
3586
3587        let printer = RirPrinter::new(&rir, &interner);
3588        let output = printer.to_string();
3589        assert!(output.contains("call foo(%0)"));
3590    }
3591
3592    #[test]
3593    fn test_printer_call_with_arg_modes() {
3594        let (mut rir, interner) = create_printer_test_rir();
3595        let arg1 = rir.add_inst(Inst {
3596            data: InstData::IntConst(1),
3597            span: Span::new(0, 1),
3598        });
3599        let arg2 = rir.add_inst(Inst {
3600            data: InstData::IntConst(2),
3601            span: Span::new(0, 1),
3602        });
3603        let arg3 = rir.add_inst(Inst {
3604            data: InstData::IntConst(3),
3605            span: Span::new(0, 1),
3606        });
3607
3608        let name = interner.get_or_intern("modify");
3609
3610        let (args_start, args_len) = rir.add_call_args(&[
3611            RirCallArg {
3612                value: arg1,
3613                mode: RirArgMode::Normal,
3614            },
3615            RirCallArg {
3616                value: arg2,
3617                mode: RirArgMode::Inout,
3618            },
3619            RirCallArg {
3620                value: arg3,
3621                mode: RirArgMode::Borrow,
3622            },
3623        ]);
3624
3625        rir.add_inst(Inst {
3626            data: InstData::Call {
3627                name,
3628                args_start,
3629                args_len,
3630            },
3631            span: Span::new(0, 20),
3632        });
3633
3634        let printer = RirPrinter::new(&rir, &interner);
3635        let output = printer.to_string();
3636        assert!(output.contains("call modify(%0, inout %1, borrow %2)"));
3637    }
3638
3639    #[test]
3640    fn test_printer_intrinsic() {
3641        let (mut rir, interner) = create_printer_test_rir();
3642        let arg = rir.add_inst(Inst {
3643            data: InstData::IntConst(42),
3644            span: Span::new(0, 2),
3645        });
3646
3647        let name = interner.get_or_intern("dbg");
3648
3649        let (args_start, args_len) = rir.add_call_args(&[RirCallArg {
3650            value: arg,
3651            mode: RirArgMode::Normal,
3652        }]);
3653
3654        rir.add_inst(Inst {
3655            data: InstData::Intrinsic {
3656                name,
3657                args_start,
3658                args_len,
3659            },
3660            span: Span::new(0, 10),
3661        });
3662
3663        let printer = RirPrinter::new(&rir, &interner);
3664        let output = printer.to_string();
3665        assert!(output.contains("intrinsic @dbg(%0)"));
3666    }
3667
3668    #[test]
3669    fn test_printer_type_intrinsic() {
3670        let (mut rir, interner) = create_printer_test_rir();
3671        let name = interner.get_or_intern("size_of");
3672        let type_arg = interner.get_or_intern("i32");
3673
3674        rir.add_inst(Inst {
3675            data: InstData::TypeIntrinsic { name, type_arg },
3676            span: Span::new(0, 15),
3677        });
3678
3679        let printer = RirPrinter::new(&rir, &interner);
3680        let output = printer.to_string();
3681        assert!(output.contains("type_intrinsic @size_of(i32)"));
3682    }
3683
3684    #[test]
3685    fn test_printer_param_ref() {
3686        let (mut rir, interner) = create_printer_test_rir();
3687        let name = interner.get_or_intern("x");
3688
3689        rir.add_inst(Inst {
3690            data: InstData::ParamRef { index: 0, name },
3691            span: Span::new(0, 1),
3692        });
3693
3694        let printer = RirPrinter::new(&rir, &interner);
3695        let output = printer.to_string();
3696        assert!(output.contains("param 0 (x)"));
3697    }
3698
3699    #[test]
3700    fn test_printer_block() {
3701        let (mut rir, interner) = create_printer_test_rir();
3702        rir.add_inst(Inst {
3703            data: InstData::Block {
3704                extra_start: 0,
3705                len: 3,
3706            },
3707            span: Span::new(0, 20),
3708        });
3709
3710        let printer = RirPrinter::new(&rir, &interner);
3711        let output = printer.to_string();
3712        assert!(output.contains("block(0, 3)"));
3713    }
3714
3715    #[test]
3716    fn test_printer_alloc() {
3717        let (mut rir, interner) = create_printer_test_rir();
3718        let init = rir.add_inst(Inst {
3719            data: InstData::IntConst(42),
3720            span: Span::new(0, 2),
3721        });
3722
3723        let name = interner.get_or_intern("x");
3724        let ty = interner.get_or_intern("i32");
3725
3726        let (directives_start, directives_len) = rir.add_directives(&[]);
3727
3728        // Normal alloc with type
3729        rir.add_inst(Inst {
3730            data: InstData::Alloc {
3731                directives_start,
3732                directives_len,
3733                name: Some(name),
3734                is_mut: false,
3735                ty: Some(ty),
3736                init,
3737            },
3738            span: Span::new(0, 15),
3739        });
3740
3741        let printer = RirPrinter::new(&rir, &interner);
3742        let output = printer.to_string();
3743        assert!(output.contains("alloc x: i32= %0"));
3744    }
3745
3746    #[test]
3747    fn test_printer_alloc_mut() {
3748        let (mut rir, interner) = create_printer_test_rir();
3749        let init = rir.add_inst(Inst {
3750            data: InstData::IntConst(42),
3751            span: Span::new(0, 2),
3752        });
3753
3754        let name = interner.get_or_intern("x");
3755
3756        let (directives_start, directives_len) = rir.add_directives(&[]);
3757
3758        rir.add_inst(Inst {
3759            data: InstData::Alloc {
3760                directives_start,
3761                directives_len,
3762                name: Some(name),
3763                is_mut: true,
3764                ty: None,
3765                init,
3766            },
3767            span: Span::new(0, 15),
3768        });
3769
3770        let printer = RirPrinter::new(&rir, &interner);
3771        let output = printer.to_string();
3772        assert!(output.contains("alloc mut x= %0"));
3773    }
3774
3775    #[test]
3776    fn test_printer_alloc_wildcard() {
3777        let (mut rir, interner) = create_printer_test_rir();
3778        let init = rir.add_inst(Inst {
3779            data: InstData::IntConst(42),
3780            span: Span::new(0, 2),
3781        });
3782
3783        let (directives_start, directives_len) = rir.add_directives(&[]);
3784
3785        rir.add_inst(Inst {
3786            data: InstData::Alloc {
3787                directives_start,
3788                directives_len,
3789                name: None,
3790                is_mut: false,
3791                ty: None,
3792                init,
3793            },
3794            span: Span::new(0, 10),
3795        });
3796
3797        let printer = RirPrinter::new(&rir, &interner);
3798        let output = printer.to_string();
3799        assert!(output.contains("alloc _= %0"));
3800    }
3801
3802    #[test]
3803    fn test_printer_var_ref() {
3804        let (mut rir, interner) = create_printer_test_rir();
3805        let name = interner.get_or_intern("x");
3806
3807        rir.add_inst(Inst {
3808            data: InstData::VarRef { name },
3809            span: Span::new(0, 1),
3810        });
3811
3812        let printer = RirPrinter::new(&rir, &interner);
3813        let output = printer.to_string();
3814        assert!(output.contains("var_ref x"));
3815    }
3816
3817    #[test]
3818    fn test_printer_assign() {
3819        let (mut rir, interner) = create_printer_test_rir();
3820        let value = rir.add_inst(Inst {
3821            data: InstData::IntConst(10),
3822            span: Span::new(0, 2),
3823        });
3824
3825        let name = interner.get_or_intern("x");
3826
3827        rir.add_inst(Inst {
3828            data: InstData::Assign { name, value },
3829            span: Span::new(0, 6),
3830        });
3831
3832        let printer = RirPrinter::new(&rir, &interner);
3833        let output = printer.to_string();
3834        assert!(output.contains("assign x = %0"));
3835    }
3836
3837    #[test]
3838    fn test_printer_struct_decl() {
3839        let (mut rir, interner) = create_printer_test_rir();
3840        let name = interner.get_or_intern("Point");
3841        let x_name = interner.get_or_intern("x");
3842        let y_name = interner.get_or_intern("y");
3843        let i32_type = interner.get_or_intern("i32");
3844
3845        let (directives_start, directives_len) = rir.add_directives(&[]);
3846        let (fields_start, fields_len) =
3847            rir.add_field_decls(&[(x_name, i32_type), (y_name, i32_type)]);
3848        let (methods_start, methods_len) = rir.add_inst_refs(&[]);
3849
3850        rir.add_inst(Inst {
3851            data: InstData::StructDecl {
3852                directives_start,
3853                directives_len,
3854                is_pub: false,
3855                is_linear: false,
3856                name,
3857                fields_start,
3858                fields_len,
3859                methods_start,
3860                methods_len,
3861            },
3862            span: Span::new(0, 30),
3863        });
3864
3865        let printer = RirPrinter::new(&rir, &interner);
3866        let output = printer.to_string();
3867        assert!(output.contains("struct Point { x: i32, y: i32 }"));
3868    }
3869
3870    #[test]
3871    fn test_printer_struct_decl_with_directive() {
3872        let (mut rir, interner) = create_printer_test_rir();
3873        let name = interner.get_or_intern("Point");
3874        let x_name = interner.get_or_intern("x");
3875        let i32_type = interner.get_or_intern("i32");
3876        let copy_name = interner.get_or_intern("copy");
3877
3878        let (directives_start, directives_len) = rir.add_directives(&[RirDirective {
3879            name: copy_name,
3880            args: vec![],
3881            span: Span::new(0, 5),
3882        }]);
3883        let (fields_start, fields_len) = rir.add_field_decls(&[(x_name, i32_type)]);
3884        let (methods_start, methods_len) = rir.add_inst_refs(&[]);
3885
3886        rir.add_inst(Inst {
3887            data: InstData::StructDecl {
3888                directives_start,
3889                directives_len,
3890                is_pub: false,
3891                is_linear: false,
3892                name,
3893                fields_start,
3894                fields_len,
3895                methods_start,
3896                methods_len,
3897            },
3898            span: Span::new(0, 30),
3899        });
3900
3901        let printer = RirPrinter::new(&rir, &interner);
3902        let output = printer.to_string();
3903        assert!(output.contains("@copy struct Point { x: i32 }"));
3904    }
3905
3906    #[test]
3907    fn test_printer_struct_init() {
3908        let (mut rir, interner) = create_printer_test_rir();
3909        let x_val = rir.add_inst(Inst {
3910            data: InstData::IntConst(10),
3911            span: Span::new(0, 2),
3912        });
3913        let y_val = rir.add_inst(Inst {
3914            data: InstData::IntConst(20),
3915            span: Span::new(0, 2),
3916        });
3917
3918        let type_name = interner.get_or_intern("Point");
3919        let x_name = interner.get_or_intern("x");
3920        let y_name = interner.get_or_intern("y");
3921
3922        let (fields_start, fields_len) = rir.add_field_inits(&[(x_name, x_val), (y_name, y_val)]);
3923
3924        rir.add_inst(Inst {
3925            data: InstData::StructInit {
3926                module: None,
3927                type_name,
3928                fields_start,
3929                fields_len,
3930            },
3931            span: Span::new(0, 25),
3932        });
3933
3934        let printer = RirPrinter::new(&rir, &interner);
3935        let output = printer.to_string();
3936        assert!(output.contains("struct_init Point { x: %0, y: %1 }"));
3937    }
3938
3939    #[test]
3940    fn test_printer_field_get() {
3941        let (mut rir, interner) = create_printer_test_rir();
3942        let base = rir.add_inst(Inst {
3943            data: InstData::IntConst(0), // placeholder for a struct value
3944            span: Span::new(0, 1),
3945        });
3946
3947        let field = interner.get_or_intern("x");
3948
3949        rir.add_inst(Inst {
3950            data: InstData::FieldGet { base, field },
3951            span: Span::new(0, 5),
3952        });
3953
3954        let printer = RirPrinter::new(&rir, &interner);
3955        let output = printer.to_string();
3956        assert!(output.contains("field_get %0.x"));
3957    }
3958
3959    #[test]
3960    fn test_printer_field_set() {
3961        let (mut rir, interner) = create_printer_test_rir();
3962        let base = rir.add_inst(Inst {
3963            data: InstData::IntConst(0), // placeholder
3964            span: Span::new(0, 1),
3965        });
3966        let value = rir.add_inst(Inst {
3967            data: InstData::IntConst(42),
3968            span: Span::new(0, 2),
3969        });
3970
3971        let field = interner.get_or_intern("x");
3972
3973        rir.add_inst(Inst {
3974            data: InstData::FieldSet { base, field, value },
3975            span: Span::new(0, 10),
3976        });
3977
3978        let printer = RirPrinter::new(&rir, &interner);
3979        let output = printer.to_string();
3980        assert!(output.contains("field_set %0.x = %1"));
3981    }
3982
3983    #[test]
3984    fn test_printer_enum_decl() {
3985        let (mut rir, interner) = create_printer_test_rir();
3986        let name = interner.get_or_intern("Color");
3987        let red = interner.get_or_intern("Red");
3988        let green = interner.get_or_intern("Green");
3989        let blue = interner.get_or_intern("Blue");
3990
3991        // Unit variants: no fields
3992        let (variants_start, variants_len) = rir.add_enum_variant_decls(&[
3993            (red, vec![], vec![]),
3994            (green, vec![], vec![]),
3995            (blue, vec![], vec![]),
3996        ]);
3997
3998        rir.add_inst(Inst {
3999            data: InstData::EnumDecl {
4000                is_pub: false,
4001                name,
4002                variants_start,
4003                variants_len,
4004            },
4005            span: Span::new(0, 35),
4006        });
4007
4008        let printer = RirPrinter::new(&rir, &interner);
4009        let output = printer.to_string();
4010        assert!(output.contains("enum Color { Red, Green, Blue }"));
4011    }
4012
4013    #[test]
4014    fn test_printer_enum_decl_with_data() {
4015        let (mut rir, interner) = create_printer_test_rir();
4016        let name = interner.get_or_intern("IntOption");
4017        let none = interner.get_or_intern("None");
4018        let some = interner.get_or_intern("Some");
4019        let i32_ty = interner.get_or_intern("i32");
4020
4021        let (variants_start, variants_len) =
4022            rir.add_enum_variant_decls(&[(none, vec![], vec![]), (some, vec![i32_ty], vec![])]);
4023
4024        rir.add_inst(Inst {
4025            data: InstData::EnumDecl {
4026                is_pub: false,
4027                name,
4028                variants_start,
4029                variants_len,
4030            },
4031            span: Span::new(0, 35),
4032        });
4033
4034        let printer = RirPrinter::new(&rir, &interner);
4035        let output = printer.to_string();
4036        assert!(output.contains("enum IntOption { None, Some(i32) }"));
4037    }
4038
4039    #[test]
4040    fn test_printer_enum_variant() {
4041        let (mut rir, interner) = create_printer_test_rir();
4042        let type_name = interner.get_or_intern("Color");
4043        let variant = interner.get_or_intern("Red");
4044
4045        rir.add_inst(Inst {
4046            data: InstData::EnumVariant {
4047                module: None,
4048                type_name,
4049                variant,
4050            },
4051            span: Span::new(0, 10),
4052        });
4053
4054        let printer = RirPrinter::new(&rir, &interner);
4055        let output = printer.to_string();
4056        assert!(output.contains("enum_variant Color::Red"));
4057    }
4058
4059    #[test]
4060    fn test_printer_array_init() {
4061        let (mut rir, interner) = create_printer_test_rir();
4062        let elem1 = rir.add_inst(Inst {
4063            data: InstData::IntConst(1),
4064            span: Span::new(0, 1),
4065        });
4066        let elem2 = rir.add_inst(Inst {
4067            data: InstData::IntConst(2),
4068            span: Span::new(0, 1),
4069        });
4070        let elem3 = rir.add_inst(Inst {
4071            data: InstData::IntConst(3),
4072            span: Span::new(0, 1),
4073        });
4074
4075        let (elems_start, elems_len) = rir.add_inst_refs(&[elem1, elem2, elem3]);
4076
4077        rir.add_inst(Inst {
4078            data: InstData::ArrayInit {
4079                elems_start,
4080                elems_len,
4081            },
4082            span: Span::new(0, 10),
4083        });
4084
4085        let printer = RirPrinter::new(&rir, &interner);
4086        let output = printer.to_string();
4087        assert!(output.contains("array_init [%0, %1, %2]"));
4088    }
4089
4090    #[test]
4091    fn test_printer_index_get() {
4092        let (mut rir, interner) = create_printer_test_rir();
4093        let base = rir.add_inst(Inst {
4094            data: InstData::IntConst(0), // placeholder for array
4095            span: Span::new(0, 1),
4096        });
4097        let index = rir.add_inst(Inst {
4098            data: InstData::IntConst(1),
4099            span: Span::new(0, 1),
4100        });
4101
4102        rir.add_inst(Inst {
4103            data: InstData::IndexGet { base, index },
4104            span: Span::new(0, 5),
4105        });
4106
4107        let printer = RirPrinter::new(&rir, &interner);
4108        let output = printer.to_string();
4109        assert!(output.contains("index_get %0[%1]"));
4110    }
4111
4112    #[test]
4113    fn test_printer_index_set() {
4114        let (mut rir, interner) = create_printer_test_rir();
4115        let base = rir.add_inst(Inst {
4116            data: InstData::IntConst(0), // placeholder for array
4117            span: Span::new(0, 1),
4118        });
4119        let index = rir.add_inst(Inst {
4120            data: InstData::IntConst(1),
4121            span: Span::new(0, 1),
4122        });
4123        let value = rir.add_inst(Inst {
4124            data: InstData::IntConst(42),
4125            span: Span::new(0, 2),
4126        });
4127
4128        rir.add_inst(Inst {
4129            data: InstData::IndexSet { base, index, value },
4130            span: Span::new(0, 10),
4131        });
4132
4133        let printer = RirPrinter::new(&rir, &interner);
4134        let output = printer.to_string();
4135        assert!(output.contains("index_set %0[%1] = %2"));
4136    }
4137
4138    // Struct with methods tests
4139    #[test]
4140    fn test_printer_struct_decl_with_methods() {
4141        let (mut rir, interner) = create_printer_test_rir();
4142
4143        // Create a method first
4144        let method_body = rir.add_inst(Inst {
4145            data: InstData::IntConst(0),
4146            span: Span::new(0, 1),
4147        });
4148        let method_name = interner.get_or_intern("get_x");
4149        let return_type = interner.get_or_intern("i32");
4150
4151        let (directives_start, directives_len) = rir.add_directives(&[]);
4152        let (params_start, params_len) = rir.add_params(&[]);
4153
4154        let method_ref = rir.add_inst(Inst {
4155            data: InstData::FnDecl {
4156                directives_start,
4157                directives_len,
4158                is_pub: false,
4159                is_unchecked: false,
4160                name: method_name,
4161                params_start,
4162                params_len,
4163                return_type,
4164                body: method_body,
4165                has_self: true,
4166            },
4167            span: Span::new(0, 30),
4168        });
4169
4170        let struct_name = interner.get_or_intern("Point");
4171        let x_field = interner.get_or_intern("x");
4172        let i32_type = interner.get_or_intern("i32");
4173
4174        let (fields_start, fields_len) = rir.add_field_decls(&[(x_field, i32_type)]);
4175        let (methods_start, methods_len) = rir.add_inst_refs(&[method_ref]);
4176
4177        rir.add_inst(Inst {
4178            data: InstData::StructDecl {
4179                directives_start,
4180                directives_len,
4181                is_pub: false,
4182                is_linear: false,
4183                name: struct_name,
4184                fields_start,
4185                fields_len,
4186                methods_start,
4187                methods_len,
4188            },
4189            span: Span::new(0, 50),
4190        });
4191
4192        let printer = RirPrinter::new(&rir, &interner);
4193        let output = printer.to_string();
4194        assert!(output.contains("struct Point { x: i32 } methods: [%1]"));
4195    }
4196
4197    #[test]
4198    fn test_printer_method_call() {
4199        let (mut rir, interner) = create_printer_test_rir();
4200        let receiver = rir.add_inst(Inst {
4201            data: InstData::IntConst(0), // placeholder for struct value
4202            span: Span::new(0, 1),
4203        });
4204        let arg = rir.add_inst(Inst {
4205            data: InstData::IntConst(10),
4206            span: Span::new(0, 2),
4207        });
4208
4209        let method = interner.get_or_intern("add");
4210
4211        let (args_start, args_len) = rir.add_call_args(&[RirCallArg {
4212            value: arg,
4213            mode: RirArgMode::Normal,
4214        }]);
4215
4216        rir.add_inst(Inst {
4217            data: InstData::MethodCall {
4218                receiver,
4219                method,
4220                args_start,
4221                args_len,
4222            },
4223            span: Span::new(0, 15),
4224        });
4225
4226        let printer = RirPrinter::new(&rir, &interner);
4227        let output = printer.to_string();
4228        assert!(output.contains("method_call %0.add(%1)"));
4229    }
4230
4231    #[test]
4232    fn test_printer_method_call_with_arg_modes() {
4233        let (mut rir, interner) = create_printer_test_rir();
4234        let receiver = rir.add_inst(Inst {
4235            data: InstData::IntConst(0),
4236            span: Span::new(0, 1),
4237        });
4238        let arg1 = rir.add_inst(Inst {
4239            data: InstData::IntConst(1),
4240            span: Span::new(0, 1),
4241        });
4242        let arg2 = rir.add_inst(Inst {
4243            data: InstData::IntConst(2),
4244            span: Span::new(0, 1),
4245        });
4246
4247        let method = interner.get_or_intern("modify");
4248
4249        let (args_start, args_len) = rir.add_call_args(&[
4250            RirCallArg {
4251                value: arg1,
4252                mode: RirArgMode::Inout,
4253            },
4254            RirCallArg {
4255                value: arg2,
4256                mode: RirArgMode::Borrow,
4257            },
4258        ]);
4259
4260        rir.add_inst(Inst {
4261            data: InstData::MethodCall {
4262                receiver,
4263                method,
4264                args_start,
4265                args_len,
4266            },
4267            span: Span::new(0, 25),
4268        });
4269
4270        let printer = RirPrinter::new(&rir, &interner);
4271        let output = printer.to_string();
4272        assert!(output.contains("method_call %0.modify(inout %1, borrow %2)"));
4273    }
4274
4275    #[test]
4276    fn test_printer_assoc_fn_call() {
4277        let (mut rir, interner) = create_printer_test_rir();
4278
4279        let type_name = interner.get_or_intern("Point");
4280        let function = interner.get_or_intern("origin");
4281
4282        let (args_start, args_len) = rir.add_call_args(&[]);
4283
4284        rir.add_inst(Inst {
4285            data: InstData::AssocFnCall {
4286                type_name,
4287                function,
4288                args_start,
4289                args_len,
4290            },
4291            span: Span::new(0, 15),
4292        });
4293
4294        let printer = RirPrinter::new(&rir, &interner);
4295        let output = printer.to_string();
4296        assert!(output.contains("assoc_fn_call Point::origin()"));
4297    }
4298
4299    #[test]
4300    fn test_printer_assoc_fn_call_with_args() {
4301        let (mut rir, interner) = create_printer_test_rir();
4302        let arg1 = rir.add_inst(Inst {
4303            data: InstData::IntConst(10),
4304            span: Span::new(0, 2),
4305        });
4306        let arg2 = rir.add_inst(Inst {
4307            data: InstData::IntConst(20),
4308            span: Span::new(0, 2),
4309        });
4310
4311        let type_name = interner.get_or_intern("Point");
4312        let function = interner.get_or_intern("new");
4313
4314        let (args_start, args_len) = rir.add_call_args(&[
4315            RirCallArg {
4316                value: arg1,
4317                mode: RirArgMode::Normal,
4318            },
4319            RirCallArg {
4320                value: arg2,
4321                mode: RirArgMode::Normal,
4322            },
4323        ]);
4324
4325        rir.add_inst(Inst {
4326            data: InstData::AssocFnCall {
4327                type_name,
4328                function,
4329                args_start,
4330                args_len,
4331            },
4332            span: Span::new(0, 20),
4333        });
4334
4335        let printer = RirPrinter::new(&rir, &interner);
4336        let output = printer.to_string();
4337        assert!(output.contains("assoc_fn_call Point::new(%0, %1)"));
4338    }
4339
4340    #[test]
4341    fn test_printer_drop_fn_decl() {
4342        let (mut rir, interner) = create_printer_test_rir();
4343        let body = rir.add_inst(Inst {
4344            data: InstData::UnitConst,
4345            span: Span::new(0, 2),
4346        });
4347
4348        let type_name = interner.get_or_intern("Resource");
4349
4350        rir.add_inst(Inst {
4351            data: InstData::DropFnDecl { type_name, body },
4352            span: Span::new(0, 30),
4353        });
4354
4355        let printer = RirPrinter::new(&rir, &interner);
4356        let output = printer.to_string();
4357        assert!(output.contains("drop fn Resource(self)"));
4358    }
4359
4360    // Match and pattern tests
4361    #[test]
4362    fn test_printer_match_wildcard() {
4363        let (mut rir, interner) = create_printer_test_rir();
4364        let scrutinee = rir.add_inst(Inst {
4365            data: InstData::IntConst(42),
4366            span: Span::new(0, 2),
4367        });
4368        let body = rir.add_inst(Inst {
4369            data: InstData::IntConst(0),
4370            span: Span::new(0, 1),
4371        });
4372
4373        let (arms_start, arms_len) =
4374            rir.add_match_arms(&[(RirPattern::Wildcard(Span::new(0, 1)), body)]);
4375
4376        rir.add_inst(Inst {
4377            data: InstData::Match {
4378                scrutinee,
4379                arms_start,
4380                arms_len,
4381            },
4382            span: Span::new(0, 20),
4383        });
4384
4385        let printer = RirPrinter::new(&rir, &interner);
4386        let output = printer.to_string();
4387        assert!(output.contains("match %0 { _ => %1 }"));
4388    }
4389
4390    #[test]
4391    fn test_printer_match_int_pattern() {
4392        let (mut rir, interner) = create_printer_test_rir();
4393        let scrutinee = rir.add_inst(Inst {
4394            data: InstData::IntConst(42),
4395            span: Span::new(0, 2),
4396        });
4397        let body1 = rir.add_inst(Inst {
4398            data: InstData::IntConst(1),
4399            span: Span::new(0, 1),
4400        });
4401        let body2 = rir.add_inst(Inst {
4402            data: InstData::IntConst(2),
4403            span: Span::new(0, 1),
4404        });
4405        let body_default = rir.add_inst(Inst {
4406            data: InstData::IntConst(0),
4407            span: Span::new(0, 1),
4408        });
4409
4410        let (arms_start, arms_len) = rir.add_match_arms(&[
4411            (RirPattern::Int(1, Span::new(0, 1)), body1),
4412            (RirPattern::Int(-5, Span::new(0, 2)), body2),
4413            (RirPattern::Wildcard(Span::new(0, 1)), body_default),
4414        ]);
4415
4416        rir.add_inst(Inst {
4417            data: InstData::Match {
4418                scrutinee,
4419                arms_start,
4420                arms_len,
4421            },
4422            span: Span::new(0, 30),
4423        });
4424
4425        let printer = RirPrinter::new(&rir, &interner);
4426        let output = printer.to_string();
4427        assert!(output.contains("match %0 { 1 => %1, -5 => %2, _ => %3 }"));
4428    }
4429
4430    #[test]
4431    fn test_printer_match_bool_pattern() {
4432        let (mut rir, interner) = create_printer_test_rir();
4433        let scrutinee = rir.add_inst(Inst {
4434            data: InstData::BoolConst(true),
4435            span: Span::new(0, 4),
4436        });
4437        let body_true = rir.add_inst(Inst {
4438            data: InstData::IntConst(1),
4439            span: Span::new(0, 1),
4440        });
4441        let body_false = rir.add_inst(Inst {
4442            data: InstData::IntConst(0),
4443            span: Span::new(0, 1),
4444        });
4445
4446        let (arms_start, arms_len) = rir.add_match_arms(&[
4447            (RirPattern::Bool(true, Span::new(0, 4)), body_true),
4448            (RirPattern::Bool(false, Span::new(0, 5)), body_false),
4449        ]);
4450
4451        rir.add_inst(Inst {
4452            data: InstData::Match {
4453                scrutinee,
4454                arms_start,
4455                arms_len,
4456            },
4457            span: Span::new(0, 30),
4458        });
4459
4460        let printer = RirPrinter::new(&rir, &interner);
4461        let output = printer.to_string();
4462        assert!(output.contains("match %0 { true => %1, false => %2 }"));
4463    }
4464
4465    #[test]
4466    fn test_printer_match_path_pattern() {
4467        let (mut rir, interner) = create_printer_test_rir();
4468        let scrutinee = rir.add_inst(Inst {
4469            data: InstData::IntConst(0), // placeholder for enum value
4470            span: Span::new(0, 1),
4471        });
4472        let body_red = rir.add_inst(Inst {
4473            data: InstData::IntConst(1),
4474            span: Span::new(0, 1),
4475        });
4476        let body_green = rir.add_inst(Inst {
4477            data: InstData::IntConst(2),
4478            span: Span::new(0, 1),
4479        });
4480        let body_default = rir.add_inst(Inst {
4481            data: InstData::IntConst(0),
4482            span: Span::new(0, 1),
4483        });
4484
4485        let color = interner.get_or_intern("Color");
4486        let red = interner.get_or_intern("Red");
4487        let green = interner.get_or_intern("Green");
4488
4489        let (arms_start, arms_len) = rir.add_match_arms(&[
4490            (
4491                RirPattern::Path {
4492                    module: None,
4493                    type_name: color,
4494                    variant: red,
4495                    span: Span::new(0, 10),
4496                },
4497                body_red,
4498            ),
4499            (
4500                RirPattern::Path {
4501                    module: None,
4502                    type_name: color,
4503                    variant: green,
4504                    span: Span::new(0, 12),
4505                },
4506                body_green,
4507            ),
4508            (RirPattern::Wildcard(Span::new(0, 1)), body_default),
4509        ]);
4510
4511        rir.add_inst(Inst {
4512            data: InstData::Match {
4513                scrutinee,
4514                arms_start,
4515                arms_len,
4516            },
4517            span: Span::new(0, 50),
4518        });
4519
4520        let printer = RirPrinter::new(&rir, &interner);
4521        let output = printer.to_string();
4522        assert!(output.contains("match %0 { Color::Red => %1, Color::Green => %2, _ => %3 }"));
4523    }
4524
4525    #[test]
4526    fn test_printer_display_trait() {
4527        let (mut rir, interner) = create_printer_test_rir();
4528        rir.add_inst(Inst {
4529            data: InstData::IntConst(42),
4530            span: Span::new(0, 2),
4531        });
4532
4533        let printer = RirPrinter::new(&rir, &interner);
4534        // Test Display trait implementation
4535        let output = format!("{}", printer);
4536        assert!(output.contains("%0 = const 42"));
4537    }
4538
4539    // ===== RIR merge tests =====
4540
4541    #[test]
4542    fn test_merge_empty_rirs() {
4543        let merged = Rir::merge(&[]);
4544        assert!(merged.is_empty());
4545        assert!(merged.function_spans().is_empty());
4546    }
4547
4548    #[test]
4549    fn test_merge_single_rir() {
4550        let mut rir = Rir::new();
4551        rir.add_inst(Inst {
4552            data: InstData::IntConst(42),
4553            span: Span::new(0, 2),
4554        });
4555        rir.add_inst(Inst {
4556            data: InstData::BoolConst(true),
4557            span: Span::new(3, 7),
4558        });
4559
4560        let merged = Rir::merge(&[rir]);
4561        assert_eq!(merged.len(), 2);
4562
4563        // Check that instructions are preserved
4564        assert!(matches!(
4565            merged.get(InstRef::from_raw(0)).data,
4566            InstData::IntConst(42)
4567        ));
4568        assert!(matches!(
4569            merged.get(InstRef::from_raw(1)).data,
4570            InstData::BoolConst(true)
4571        ));
4572    }
4573
4574    #[test]
4575    fn test_merge_two_rirs_simple() {
4576        // RIR 1: just an int constant
4577        let mut rir1 = Rir::new();
4578        rir1.add_inst(Inst {
4579            data: InstData::IntConst(10),
4580            span: Span::new(0, 2),
4581        });
4582
4583        // RIR 2: another int constant
4584        let mut rir2 = Rir::new();
4585        rir2.add_inst(Inst {
4586            data: InstData::IntConst(20),
4587            span: Span::new(5, 7),
4588        });
4589
4590        let merged = Rir::merge(&[rir1, rir2]);
4591        assert_eq!(merged.len(), 2);
4592
4593        // First instruction from rir1
4594        assert!(matches!(
4595            merged.get(InstRef::from_raw(0)).data,
4596            InstData::IntConst(10)
4597        ));
4598        // Second instruction from rir2 (renumbered to index 1)
4599        assert!(matches!(
4600            merged.get(InstRef::from_raw(1)).data,
4601            InstData::IntConst(20)
4602        ));
4603    }
4604
4605    #[test]
4606    fn test_merge_renumbers_inst_refs() {
4607        // RIR 1: const and an add that references it
4608        let mut rir1 = Rir::new();
4609        let const1 = rir1.add_inst(Inst {
4610            data: InstData::IntConst(5),
4611            span: Span::new(0, 1),
4612        });
4613        rir1.add_inst(Inst {
4614            data: InstData::Add {
4615                lhs: const1,
4616                rhs: const1,
4617            },
4618            span: Span::new(2, 5),
4619        });
4620
4621        // RIR 2: const and an add that references it (local indices)
4622        let mut rir2 = Rir::new();
4623        let const2 = rir2.add_inst(Inst {
4624            data: InstData::IntConst(10),
4625            span: Span::new(10, 12),
4626        });
4627        rir2.add_inst(Inst {
4628            data: InstData::Add {
4629                lhs: const2,
4630                rhs: const2,
4631            },
4632            span: Span::new(12, 16),
4633        });
4634
4635        let merged = Rir::merge(&[rir1, rir2]);
4636        assert_eq!(merged.len(), 4);
4637
4638        // Check rir1's add still references %0
4639        if let InstData::Add { lhs, rhs } = &merged.get(InstRef::from_raw(1)).data {
4640            assert_eq!(lhs.as_u32(), 0);
4641            assert_eq!(rhs.as_u32(), 0);
4642        } else {
4643            panic!("Expected Add instruction at index 1");
4644        }
4645
4646        // Check rir2's add now references %2 (renumbered from %0)
4647        if let InstData::Add { lhs, rhs } = &merged.get(InstRef::from_raw(3)).data {
4648            assert_eq!(lhs.as_u32(), 2);
4649            assert_eq!(rhs.as_u32(), 2);
4650        } else {
4651            panic!("Expected Add instruction at index 3");
4652        }
4653    }
4654
4655    #[test]
4656    fn test_merge_renumbers_extra_data() {
4657        // RIR 1: function call with args in extra
4658        let mut rir1 = Rir::new();
4659        let interner = ThreadedRodeo::new();
4660        let fn_name = interner.get_or_intern("foo");
4661
4662        let const1 = rir1.add_inst(Inst {
4663            data: InstData::IntConst(1),
4664            span: Span::new(0, 1),
4665        });
4666        let (args_start, args_len) = rir1.add_call_args(&[RirCallArg {
4667            value: const1,
4668            mode: RirArgMode::Normal,
4669        }]);
4670        rir1.add_inst(Inst {
4671            data: InstData::Call {
4672                name: fn_name,
4673                args_start,
4674                args_len,
4675            },
4676            span: Span::new(2, 8),
4677        });
4678
4679        // RIR 2: function call with args in extra
4680        let mut rir2 = Rir::new();
4681        let const2 = rir2.add_inst(Inst {
4682            data: InstData::IntConst(2),
4683            span: Span::new(10, 11),
4684        });
4685        let (args_start2, args_len2) = rir2.add_call_args(&[RirCallArg {
4686            value: const2,
4687            mode: RirArgMode::Normal,
4688        }]);
4689        rir2.add_inst(Inst {
4690            data: InstData::Call {
4691                name: fn_name,
4692                args_start: args_start2,
4693                args_len: args_len2,
4694            },
4695            span: Span::new(12, 18),
4696        });
4697
4698        let merged = Rir::merge(&[rir1, rir2]);
4699        assert_eq!(merged.len(), 4);
4700
4701        // Check rir1's call still has correct args_start
4702        if let InstData::Call {
4703            args_start,
4704            args_len,
4705            ..
4706        } = &merged.get(InstRef::from_raw(1)).data
4707        {
4708            let args = merged.get_call_args(*args_start, *args_len);
4709            assert_eq!(args.len(), 1);
4710            assert_eq!(args[0].value.as_u32(), 0); // Still references const1 at %0
4711        } else {
4712            panic!("Expected Call instruction at index 1");
4713        }
4714
4715        // Check rir2's call has updated args_start and renumbered arg value
4716        if let InstData::Call {
4717            args_start,
4718            args_len,
4719            ..
4720        } = &merged.get(InstRef::from_raw(3)).data
4721        {
4722            let args = merged.get_call_args(*args_start, *args_len);
4723            assert_eq!(args.len(), 1);
4724            assert_eq!(args[0].value.as_u32(), 2); // Now references const2 at %2
4725        } else {
4726            panic!("Expected Call instruction at index 3");
4727        }
4728    }
4729
4730    #[test]
4731    fn test_merge_function_spans() {
4732        let interner = ThreadedRodeo::new();
4733        let main_name = interner.get_or_intern("main");
4734        let helper_name = interner.get_or_intern("helper");
4735
4736        // RIR 1: main function
4737        let mut rir1 = Rir::new();
4738        let body_start1 = InstRef::from_raw(rir1.current_inst_index());
4739        let const1 = rir1.add_inst(Inst {
4740            data: InstData::IntConst(0),
4741            span: Span::new(0, 1),
4742        });
4743        let (params_start, params_len) = rir1.add_params(&[]);
4744        let (dirs_start, dirs_len) = rir1.add_directives(&[]);
4745        let ret_type = interner.get_or_intern("i32");
4746        let decl1 = rir1.add_inst(Inst {
4747            data: InstData::FnDecl {
4748                directives_start: dirs_start,
4749                directives_len: dirs_len,
4750                is_pub: false,
4751                is_unchecked: false,
4752                name: main_name,
4753                params_start,
4754                params_len,
4755                return_type: ret_type,
4756                body: const1,
4757                has_self: false,
4758            },
4759            span: Span::new(0, 10),
4760        });
4761        rir1.add_function_span(FunctionSpan::new(main_name, body_start1, decl1));
4762
4763        // RIR 2: helper function
4764        let mut rir2 = Rir::new();
4765        let body_start2 = InstRef::from_raw(rir2.current_inst_index());
4766        let const2 = rir2.add_inst(Inst {
4767            data: InstData::IntConst(42),
4768            span: Span::new(20, 22),
4769        });
4770        let (params_start2, params_len2) = rir2.add_params(&[]);
4771        let (dirs_start2, dirs_len2) = rir2.add_directives(&[]);
4772        let decl2 = rir2.add_inst(Inst {
4773            data: InstData::FnDecl {
4774                directives_start: dirs_start2,
4775                directives_len: dirs_len2,
4776                is_pub: false,
4777                is_unchecked: false,
4778                name: helper_name,
4779                params_start: params_start2,
4780                params_len: params_len2,
4781                return_type: ret_type,
4782                body: const2,
4783                has_self: false,
4784            },
4785            span: Span::new(20, 35),
4786        });
4787        rir2.add_function_span(FunctionSpan::new(helper_name, body_start2, decl2));
4788
4789        let merged = Rir::merge(&[rir1, rir2]);
4790
4791        // Check we have 2 function spans
4792        assert_eq!(merged.function_spans().len(), 2);
4793
4794        // Check main function span (from rir1, indices unchanged)
4795        let main_span = &merged.function_spans()[0];
4796        assert_eq!(main_span.name, main_name);
4797        assert_eq!(main_span.body_start.as_u32(), 0);
4798        assert_eq!(main_span.decl.as_u32(), 1);
4799
4800        // Check helper function span (from rir2, indices shifted by 2)
4801        let helper_span = &merged.function_spans()[1];
4802        assert_eq!(helper_span.name, helper_name);
4803        assert_eq!(helper_span.body_start.as_u32(), 2); // Was 0, now 0 + 2 = 2
4804        assert_eq!(helper_span.decl.as_u32(), 3); // Was 1, now 1 + 2 = 3
4805    }
4806
4807    #[test]
4808    fn test_merge_three_rirs() {
4809        let mut rir1 = Rir::new();
4810        rir1.add_inst(Inst {
4811            data: InstData::IntConst(1),
4812            span: Span::new(0, 1),
4813        });
4814
4815        let mut rir2 = Rir::new();
4816        rir2.add_inst(Inst {
4817            data: InstData::IntConst(2),
4818            span: Span::new(10, 11),
4819        });
4820
4821        let mut rir3 = Rir::new();
4822        rir3.add_inst(Inst {
4823            data: InstData::IntConst(3),
4824            span: Span::new(20, 21),
4825        });
4826
4827        let merged = Rir::merge(&[rir1, rir2, rir3]);
4828        assert_eq!(merged.len(), 3);
4829
4830        assert!(matches!(
4831            merged.get(InstRef::from_raw(0)).data,
4832            InstData::IntConst(1)
4833        ));
4834        assert!(matches!(
4835            merged.get(InstRef::from_raw(1)).data,
4836            InstData::IntConst(2)
4837        ));
4838        assert!(matches!(
4839            merged.get(InstRef::from_raw(2)).data,
4840            InstData::IntConst(3)
4841        ));
4842    }
4843
4844    #[test]
4845    fn test_merge_preserves_spans() {
4846        let mut rir1 = Rir::new();
4847        rir1.add_inst(Inst {
4848            data: InstData::IntConst(1),
4849            span: Span::new(5, 10),
4850        });
4851
4852        let mut rir2 = Rir::new();
4853        rir2.add_inst(Inst {
4854            data: InstData::IntConst(2),
4855            span: Span::new(100, 105),
4856        });
4857
4858        let merged = Rir::merge(&[rir1, rir2]);
4859
4860        // Spans should be preserved exactly
4861        assert_eq!(merged.get(InstRef::from_raw(0)).span, Span::new(5, 10));
4862        assert_eq!(merged.get(InstRef::from_raw(1)).span, Span::new(100, 105));
4863    }
4864}