Skip to main content

gruel_rir/
print.rs

1//! Pretty-printer for RIR.
2//!
3//! Renders a full RIR program (instructions, patterns, function spans,
4//! interface decls) in a human-readable, indented format. Used by the
5//! `--emit rir` CLI flag and by RIR golden tests.
6
7use std::fmt;
8
9use gruel_builtins::Posture;
10
11use crate::inst::{InstData, Rir, RirArgMode, RirCallArg, RirParamMode, RirPattern};
12
13/// Printer for RIR that resolves symbols to their string values.
14pub struct RirPrinter<'a, 'b> {
15    rir: &'a Rir,
16    interner: &'b lasso::ThreadedRodeo,
17}
18
19impl<'a, 'b> RirPrinter<'a, 'b> {
20    /// Create a new RIR printer.
21    pub fn new(rir: &'a Rir, interner: &'b lasso::ThreadedRodeo) -> Self {
22        Self { rir, interner }
23    }
24
25    /// Format a call argument with its mode prefix.
26    fn format_call_arg(arg: &RirCallArg) -> String {
27        match arg.mode {
28            RirArgMode::MutRef => format!("mut_ref {}", arg.value),
29            RirArgMode::Ref => format!("ref {}", arg.value),
30            RirArgMode::Normal => format!("{}", arg.value),
31        }
32    }
33
34    /// Format a list of call arguments.
35    fn format_call_args(args: &[RirCallArg]) -> String {
36        args.iter()
37            .map(Self::format_call_arg)
38            .collect::<Vec<_>>()
39            .join(", ")
40    }
41
42    /// Format a pattern for printing.
43    fn format_pattern(&self, pat: &RirPattern) -> String {
44        match pat {
45            RirPattern::Wildcard(_) => "_".to_string(),
46            RirPattern::Int(n, _) => n.to_string(),
47            RirPattern::Bool(b, _) => b.to_string(),
48            RirPattern::Path {
49                module,
50                type_name,
51                variant,
52                ..
53            } => {
54                let prefix = if let Some(module_ref) = module {
55                    format!("%{}..", module_ref.as_u32())
56                } else {
57                    String::new()
58                };
59                format!(
60                    "{}{}::{}",
61                    prefix,
62                    self.interner.resolve(type_name),
63                    self.interner.resolve(variant)
64                )
65            }
66            RirPattern::DataVariant {
67                module,
68                type_name,
69                variant,
70                bindings,
71                ..
72            } => {
73                let prefix = if let Some(module_ref) = module {
74                    format!("%{}..", module_ref.as_u32())
75                } else {
76                    String::new()
77                };
78                let binding_strs: Vec<String> = bindings
79                    .iter()
80                    .map(|b| {
81                        if b.is_wildcard {
82                            "_".to_string()
83                        } else {
84                            let name = b
85                                .name
86                                .map(|s| self.interner.resolve(&s).to_string())
87                                .unwrap_or_else(|| "_".to_string());
88                            if b.is_mut {
89                                format!("mut {}", name)
90                            } else {
91                                name
92                            }
93                        }
94                    })
95                    .collect();
96                format!(
97                    "{}{}::{}({})",
98                    prefix,
99                    self.interner.resolve(type_name),
100                    self.interner.resolve(variant),
101                    binding_strs.join(", ")
102                )
103            }
104            RirPattern::StructVariant {
105                module,
106                type_name,
107                variant,
108                field_bindings,
109                ..
110            } => {
111                let prefix = if let Some(module_ref) = module {
112                    format!("%{}..", module_ref.as_u32())
113                } else {
114                    String::new()
115                };
116                let field_strs: Vec<String> = field_bindings
117                    .iter()
118                    .map(|fb| {
119                        let field = self.interner.resolve(&fb.field_name);
120                        if fb.binding.is_wildcard {
121                            format!("{}: _", field)
122                        } else {
123                            let name = fb
124                                .binding
125                                .name
126                                .map(|s| self.interner.resolve(&s).to_string())
127                                .unwrap_or_else(|| "_".to_string());
128                            if fb.binding.is_mut {
129                                format!("{}: mut {}", field, name)
130                            } else if name == field {
131                                field.to_string()
132                            } else {
133                                format!("{}: {}", field, name)
134                            }
135                        }
136                    })
137                    .collect();
138                format!(
139                    "{}{}::{} {{ {} }}",
140                    prefix,
141                    self.interner.resolve(type_name),
142                    self.interner.resolve(variant),
143                    field_strs.join(", ")
144                )
145            }
146            RirPattern::Ident { name, is_mut, .. } => {
147                let n = self.interner.resolve(name);
148                if *is_mut {
149                    format!("mut {}", n)
150                } else {
151                    n.to_string()
152                }
153            }
154            RirPattern::Tuple { elems, .. } => {
155                let parts: Vec<String> = elems.iter().map(|e| self.format_pattern(e)).collect();
156                format!("({})", parts.join(", "))
157            }
158            RirPattern::Struct {
159                module,
160                type_name,
161                fields,
162                has_rest,
163                ..
164            } => {
165                let prefix = if let Some(module_ref) = module {
166                    format!("%{}..", module_ref.as_u32())
167                } else {
168                    String::new()
169                };
170                let mut parts: Vec<String> = fields
171                    .iter()
172                    .map(|f| {
173                        format!(
174                            "{}: {}",
175                            self.interner.resolve(&f.field_name),
176                            self.format_pattern(&f.pattern)
177                        )
178                    })
179                    .collect();
180                if *has_rest {
181                    parts.push("..".to_string());
182                }
183                format!(
184                    "{}{} {{ {} }}",
185                    prefix,
186                    self.interner.resolve(type_name),
187                    parts.join(", ")
188                )
189            }
190            RirPattern::ComptimeUnrollArm {
191                binding, iterable, ..
192            } => {
193                format!(
194                    "comptime_unroll for {} in %{}",
195                    self.interner.resolve(binding),
196                    iterable.as_u32()
197                )
198            }
199        }
200    }
201
202    /// Format the RIR as a string.
203    pub fn render(&self) -> String {
204        use std::fmt::Write;
205
206        let mut out = String::new();
207        for (inst_ref, inst) in self.rir.iter() {
208            write!(out, "{} = ", inst_ref).unwrap();
209            match &inst.data {
210                // Constants
211                InstData::IntConst(v) => writeln!(out, "const {}", v).unwrap(),
212                InstData::FloatConst(bits) => {
213                    writeln!(out, "const {}", f64::from_bits(*bits)).unwrap()
214                }
215                InstData::BoolConst(v) => writeln!(out, "const {}", v).unwrap(),
216                InstData::CharConst(v) => writeln!(out, "const 'U+{:04X}'", v).unwrap(),
217                InstData::StringConst(s) => {
218                    writeln!(out, "const {:?}", self.interner.resolve(s)).unwrap()
219                }
220                InstData::UnitConst => writeln!(out, "const ()").unwrap(),
221
222                InstData::Bin { op, lhs, rhs } => writeln!(out, "{} {}, {}", op, lhs, rhs).unwrap(),
223                InstData::Unary { op, operand } => writeln!(out, "{} {}", op, operand).unwrap(),
224                InstData::MakeRef { operand, is_mut } => writeln!(
225                    out,
226                    "make_ref{} {}",
227                    if *is_mut { "_mut" } else { "" },
228                    operand
229                )
230                .unwrap(),
231                InstData::BareRangeSubscript => writeln!(out, "bare_range_subscript").unwrap(),
232                InstData::MakeSlice {
233                    base,
234                    lo,
235                    hi,
236                    is_mut,
237                } => {
238                    write!(
239                        out,
240                        "make_slice{} {}",
241                        if *is_mut { "_mut" } else { "" },
242                        base
243                    )
244                    .unwrap();
245                    if let Some(lo) = lo {
246                        write!(out, ", lo={}", lo).unwrap();
247                    }
248                    if let Some(hi) = hi {
249                        write!(out, ", hi={}", hi).unwrap();
250                    }
251                    writeln!(out).unwrap();
252                }
253
254                // Control flow
255                InstData::Branch {
256                    cond,
257                    then_block,
258                    else_block,
259                    is_comptime,
260                } => {
261                    let kw = if *is_comptime {
262                        "comptime_branch"
263                    } else {
264                        "branch"
265                    };
266                    if let Some(else_b) = else_block {
267                        writeln!(out, "{} {}, {}, {}", kw, cond, then_block, else_b).unwrap();
268                    } else {
269                        writeln!(out, "{} {}, {}", kw, cond, then_block).unwrap();
270                    }
271                }
272                InstData::Loop { cond, body } => writeln!(out, "loop {}, {}", cond, body).unwrap(),
273                InstData::For {
274                    binding,
275                    is_mut,
276                    iterable,
277                    body,
278                } => {
279                    let mut_str = if *is_mut { "mut " } else { "" };
280                    writeln!(
281                        out,
282                        "for {}{} in {}, {}",
283                        mut_str,
284                        self.interner.resolve(binding),
285                        iterable,
286                        body
287                    )
288                    .unwrap()
289                }
290                InstData::InfiniteLoop { body } => writeln!(out, "infinite_loop {}", body).unwrap(),
291                InstData::Match {
292                    scrutinee,
293                    arms_start,
294                    arms_len,
295                } => {
296                    let arms = self.rir.get_match_arms(*arms_start, *arms_len);
297                    let arms_str: Vec<String> = arms
298                        .iter()
299                        .map(|(pat, body)| format!("{} => {}", self.format_pattern(pat), body))
300                        .collect();
301                    writeln!(out, "match {} {{ {} }}", scrutinee, arms_str.join(", ")).unwrap();
302                }
303                InstData::Break => writeln!(out, "break").unwrap(),
304                InstData::Continue => writeln!(out, "continue").unwrap(),
305
306                // Functions
307                InstData::FnDecl {
308                    directives_start: _,
309                    directives_len: _,
310                    is_pub,
311                    is_unchecked,
312                    name,
313                    params_start,
314                    params_len,
315                    return_type,
316                    body,
317                    has_self,
318                    receiver_mode: _,
319                } => {
320                    let pub_str = if *is_pub { "pub " } else { "" };
321                    let unchecked_str = if *is_unchecked { "unchecked " } else { "" };
322                    let name_str = self.interner.resolve(name);
323                    let ret_str = self.interner.resolve(return_type);
324                    let self_str = if *has_self { "self, " } else { "" };
325                    let params = self.rir.get_params(*params_start, *params_len);
326                    let params_str: Vec<String> = params
327                        .iter()
328                        .map(|p| {
329                            let mode_prefix = match p.mode {
330                                RirParamMode::MutRef => "mut_ref ",
331                                RirParamMode::Ref => "ref ",
332                                RirParamMode::Comptime => "comptime ",
333                                RirParamMode::Normal => "",
334                            };
335                            format!(
336                                "{}{}: {}",
337                                mode_prefix,
338                                self.interner.resolve(&p.name),
339                                self.interner.resolve(&p.ty)
340                            )
341                        })
342                        .collect();
343                    writeln!(
344                        out,
345                        "{}{}fn {}({}{}) -> {} {{",
346                        pub_str,
347                        unchecked_str,
348                        name_str,
349                        self_str,
350                        params_str.join(", "),
351                        ret_str
352                    )
353                    .unwrap();
354                    writeln!(out, "    {}", body).unwrap();
355                    writeln!(out, "}}").unwrap();
356                }
357                InstData::ConstDecl {
358                    directives_start: _,
359                    directives_len: _,
360                    is_pub,
361                    name,
362                    ty,
363                    init,
364                } => {
365                    let pub_str = if *is_pub { "pub " } else { "" };
366                    let name_str = self.interner.resolve(name);
367                    let ty_str = ty
368                        .map(|t| format!(": {}", self.interner.resolve(&t)))
369                        .unwrap_or_default();
370                    writeln!(out, "{}const {}{} = {}", pub_str, name_str, ty_str, init).unwrap();
371                }
372                InstData::Ret(inner) => {
373                    if let Some(inner) = inner {
374                        writeln!(out, "ret {}", inner).unwrap();
375                    } else {
376                        writeln!(out, "ret").unwrap();
377                    }
378                }
379                InstData::Call {
380                    name,
381                    args_start,
382                    args_len,
383                } => {
384                    let name_str = self.interner.resolve(name);
385                    let args = self.rir.get_call_args(*args_start, *args_len);
386                    writeln!(out, "call {}({})", name_str, Self::format_call_args(&args)).unwrap();
387                }
388                InstData::Intrinsic {
389                    name,
390                    args_start,
391                    args_len,
392                } => {
393                    let name_str = self.interner.resolve(name);
394                    let args = self.rir.get_inst_refs(*args_start, *args_len);
395                    let args_str: Vec<String> = args.iter().map(|a| format!("{}", a)).collect();
396                    writeln!(out, "intrinsic @{}({})", name_str, args_str.join(", ")).unwrap();
397                }
398                InstData::TypeIntrinsic { name, type_arg } => {
399                    let name_str = self.interner.resolve(name);
400                    let type_str = self.interner.resolve(type_arg);
401                    writeln!(out, "type_intrinsic @{}({})", name_str, type_str).unwrap();
402                }
403                InstData::TypeInterfaceIntrinsic {
404                    name,
405                    type_arg,
406                    type_inst,
407                    interface_arg,
408                } => {
409                    let name_str = self.interner.resolve(name);
410                    let iface_str = self.interner.resolve(interface_arg);
411                    if let Some(t) = type_inst {
412                        writeln!(
413                            out,
414                            "type_intrinsic @{}(%{}, {})",
415                            name_str,
416                            t.as_u32(),
417                            iface_str
418                        )
419                        .unwrap();
420                    } else {
421                        let type_str = self.interner.resolve(type_arg);
422                        writeln!(
423                            out,
424                            "type_intrinsic @{}({}, {})",
425                            name_str, type_str, iface_str
426                        )
427                        .unwrap();
428                    }
429                }
430                InstData::ParamRef { index, name } => {
431                    writeln!(out, "param {} ({})", index, self.interner.resolve(name)).unwrap();
432                }
433                InstData::Block { extra_start, len } => {
434                    writeln!(out, "block({}, {})", extra_start, len).unwrap();
435                }
436
437                // Variables
438                InstData::Alloc {
439                    directives_start: _,
440                    directives_len: _,
441                    name,
442                    is_mut,
443                    ty,
444                    init,
445                } => {
446                    let name_str = name
447                        .map(|n| self.interner.resolve(&n).to_string())
448                        .unwrap_or_else(|| "_".to_string());
449                    let mut_str = if *is_mut { "mut " } else { "" };
450                    let ty_str = ty
451                        .map(|t| format!(": {}", self.interner.resolve(&t)))
452                        .unwrap_or_default();
453                    writeln!(out, "alloc {}{}{}= {}", mut_str, name_str, ty_str, init).unwrap();
454                }
455                InstData::StructDestructure {
456                    type_name,
457                    fields_start,
458                    fields_len,
459                    init,
460                } => {
461                    let type_str = self.interner.resolve(type_name);
462                    let fields = self.rir.get_destructure_fields(*fields_start, *fields_len);
463                    let field_strs: Vec<String> = fields
464                        .iter()
465                        .map(|f| {
466                            let name = self.interner.resolve(&f.field_name);
467                            if f.is_wildcard {
468                                format!("{}: _", name)
469                            } else if let Some(binding) = f.binding_name {
470                                let binding_str = self.interner.resolve(&binding);
471                                format!("{}: {}", name, binding_str)
472                            } else {
473                                name.to_string()
474                            }
475                        })
476                        .collect();
477                    writeln!(
478                        out,
479                        "destructure {} {{ {} }} = {}",
480                        type_str,
481                        field_strs.join(", "),
482                        init
483                    )
484                    .unwrap();
485                }
486                InstData::VarRef { name } => {
487                    writeln!(out, "var_ref {}", self.interner.resolve(name)).unwrap();
488                }
489                InstData::Assign { name, value } => {
490                    writeln!(out, "assign {} = {}", self.interner.resolve(name), value).unwrap();
491                }
492
493                // Structs
494                InstData::StructDecl {
495                    directives_start,
496                    directives_len,
497                    is_pub,
498                    posture,
499                    name,
500                    fields_start,
501                    fields_len,
502                    methods_start,
503                    methods_len,
504                } => {
505                    let pub_str = if *is_pub { "pub " } else { "" };
506                    let name_str = self.interner.resolve(name);
507                    let fields = self.rir.get_field_decls(*fields_start, *fields_len);
508                    let fields_str: Vec<String> = fields
509                        .iter()
510                        .map(|(fname, ftype)| {
511                            format!(
512                                "{}: {}",
513                                self.interner.resolve(fname),
514                                self.interner.resolve(ftype)
515                            )
516                        })
517                        .collect();
518                    let directives = self.rir.get_directives(*directives_start, *directives_len);
519                    let posture_dir = match posture {
520                        Posture::Copy => Some("@mark(copy)"),
521                        Posture::Linear => Some("@mark(linear)"),
522                        Posture::Affine => None,
523                    };
524                    let mut dir_names: Vec<String> =
525                        posture_dir.into_iter().map(|s| s.to_string()).collect();
526                    for d in directives {
527                        dir_names.push(format!("@{}", self.interner.resolve(&d.name)));
528                    }
529                    let directives_str = if dir_names.is_empty() {
530                        String::new()
531                    } else {
532                        format!("{} ", dir_names.join(" "))
533                    };
534                    let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
535                    let methods_str = if methods.is_empty() {
536                        String::new()
537                    } else {
538                        let method_refs: Vec<String> =
539                            methods.iter().map(|m| format!("{}", m)).collect();
540                        format!(" methods: [{}]", method_refs.join(", "))
541                    };
542                    writeln!(
543                        out,
544                        "{}{}struct {} {{ {} }}{}",
545                        directives_str,
546                        pub_str,
547                        name_str,
548                        fields_str.join(", "),
549                        methods_str
550                    )
551                    .unwrap();
552                }
553                InstData::StructInit {
554                    module,
555                    type_name,
556                    fields_start,
557                    fields_len,
558                } => {
559                    let module_str = module.map(|m| format!("{}.", m)).unwrap_or_default();
560                    let type_str = self.interner.resolve(type_name);
561                    let fields = self.rir.get_field_inits(*fields_start, *fields_len);
562                    let fields_str: Vec<String> = fields
563                        .iter()
564                        .map(|(fname, value)| {
565                            format!("{}: {}", self.interner.resolve(fname), value)
566                        })
567                        .collect();
568                    writeln!(
569                        out,
570                        "struct_init {}{} {{ {} }}",
571                        module_str,
572                        type_str,
573                        fields_str.join(", ")
574                    )
575                    .unwrap();
576                }
577                InstData::FieldGet { base, field } => {
578                    writeln!(out, "field_get {}.{}", base, self.interner.resolve(field)).unwrap();
579                }
580                InstData::FieldSet { base, field, value } => {
581                    writeln!(
582                        out,
583                        "field_set {}.{} = {}",
584                        base,
585                        self.interner.resolve(field),
586                        value
587                    )
588                    .unwrap();
589                }
590
591                // Enums
592                InstData::EnumDecl {
593                    is_pub,
594                    posture,
595                    name,
596                    variants_start,
597                    variants_len,
598                    methods_start,
599                    methods_len,
600                    directives_start: _,
601                    directives_len: _,
602                } => {
603                    let pub_str = if *is_pub { "pub " } else { "" };
604                    let posture_str = match posture {
605                        Posture::Copy => "@mark(copy) ",
606                        Posture::Linear => "@mark(linear) ",
607                        Posture::Affine => "",
608                    };
609                    let name_str = self.interner.resolve(name);
610                    let variants = self
611                        .rir
612                        .get_enum_variant_decls(*variants_start, *variants_len);
613                    let variants_str: Vec<String> = variants
614                        .iter()
615                        .map(|(v, field_types, field_names)| {
616                            let vname = self.interner.resolve(v).to_string();
617                            if field_types.is_empty() {
618                                vname
619                            } else if field_names.is_empty() {
620                                // Tuple variant
621                                let field_strs: Vec<&str> = field_types
622                                    .iter()
623                                    .map(|f| self.interner.resolve(f))
624                                    .collect();
625                                format!("{}({})", vname, field_strs.join(", "))
626                            } else {
627                                // Struct variant
628                                let field_strs: Vec<String> = field_names
629                                    .iter()
630                                    .zip(field_types.iter())
631                                    .map(|(n, t)| {
632                                        format!(
633                                            "{}: {}",
634                                            self.interner.resolve(n),
635                                            self.interner.resolve(t)
636                                        )
637                                    })
638                                    .collect();
639                                format!("{} {{ {} }}", vname, field_strs.join(", "))
640                            }
641                        })
642                        .collect();
643                    let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
644                    let methods_str: Vec<String> =
645                        methods.iter().map(|m| format!("fn {}", m)).collect();
646                    let body_parts: Vec<String> = variants_str
647                        .into_iter()
648                        .chain(methods_str.into_iter())
649                        .collect();
650                    writeln!(
651                        out,
652                        "{}{}enum {} {{ {} }}",
653                        posture_str,
654                        pub_str,
655                        name_str,
656                        body_parts.join(", ")
657                    )
658                    .unwrap();
659                }
660                InstData::EnumVariant {
661                    module,
662                    type_name,
663                    variant,
664                } => {
665                    let module_str = module.map(|m| format!("{}.", m)).unwrap_or_default();
666                    writeln!(
667                        out,
668                        "enum_variant {}{}::{}",
669                        module_str,
670                        self.interner.resolve(type_name),
671                        self.interner.resolve(variant)
672                    )
673                    .unwrap();
674                }
675                InstData::EnumStructVariant {
676                    module,
677                    type_name,
678                    variant,
679                    fields_start,
680                    fields_len,
681                } => {
682                    let module_str = module.map(|m| format!("{}.", m)).unwrap_or_default();
683                    let fields = self.rir.get_field_inits(*fields_start, *fields_len);
684                    let field_strs: Vec<String> = fields
685                        .iter()
686                        .map(|(name, value)| format!("{}: {}", self.interner.resolve(name), value))
687                        .collect();
688                    writeln!(
689                        out,
690                        "enum_struct_variant {}{}::{} {{ {} }}",
691                        module_str,
692                        self.interner.resolve(type_name),
693                        self.interner.resolve(variant),
694                        field_strs.join(", ")
695                    )
696                    .unwrap();
697                }
698
699                // Arrays
700                InstData::ArrayInit {
701                    elems_start,
702                    elems_len,
703                } => {
704                    let elements = self.rir.get_inst_refs(*elems_start, *elems_len);
705                    let elems_str: Vec<String> =
706                        elements.iter().map(|e| format!("{}", e)).collect();
707                    writeln!(out, "array_init [{}]", elems_str.join(", ")).unwrap();
708                }
709                InstData::IndexGet { base, index } => {
710                    writeln!(out, "index_get {}[{}]", base, index).unwrap();
711                }
712                InstData::IndexSet { base, index, value } => {
713                    writeln!(out, "index_set {}[{}] = {}", base, index, value).unwrap();
714                }
715
716                // Methods
717                InstData::MethodCall {
718                    receiver,
719                    method,
720                    args_start,
721                    args_len,
722                } => {
723                    let args = self.rir.get_call_args(*args_start, *args_len);
724                    writeln!(
725                        out,
726                        "method_call {}.{}({})",
727                        receiver,
728                        self.interner.resolve(method),
729                        Self::format_call_args(&args)
730                    )
731                    .unwrap();
732                }
733                InstData::AssocFnCall {
734                    type_name,
735                    function,
736                    args_start,
737                    args_len,
738                } => {
739                    let args = self.rir.get_call_args(*args_start, *args_len);
740                    writeln!(
741                        out,
742                        "assoc_fn_call {}::{}({})",
743                        self.interner.resolve(type_name),
744                        self.interner.resolve(function),
745                        Self::format_call_args(&args)
746                    )
747                    .unwrap();
748                }
749
750                // Interfaces
751                InstData::InterfaceDecl {
752                    is_pub,
753                    name,
754                    methods_start,
755                    methods_len,
756                    directives_start: _,
757                    directives_len: _,
758                } => {
759                    let pub_str = if *is_pub { "pub " } else { "" };
760                    let name_str = self.interner.resolve(name);
761                    let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
762                    let method_refs: Vec<String> =
763                        methods.iter().map(|m| format!("{}", m)).collect();
764                    writeln!(
765                        out,
766                        "{}interface {} {{ {} }}",
767                        pub_str,
768                        name_str,
769                        method_refs.join(", ")
770                    )
771                    .unwrap();
772                }
773                InstData::InterfaceMethodSig {
774                    name,
775                    params_start,
776                    params_len,
777                    return_type,
778                    receiver_mode,
779                    is_unchecked,
780                    directives_start: _,
781                    directives_len: _,
782                } => {
783                    let params = self.rir.get_params(*params_start, *params_len);
784                    let params_str: Vec<String> = params
785                        .iter()
786                        .map(|p| {
787                            format!(
788                                "{}: {}",
789                                self.interner.resolve(&p.name),
790                                self.interner.resolve(&p.ty)
791                            )
792                        })
793                        .collect();
794                    let recv = match *receiver_mode {
795                        1 => "inout self",
796                        2 => "borrow self",
797                        _ => "self",
798                    };
799                    let unchecked_str = if *is_unchecked { "unchecked " } else { "" };
800                    writeln!(
801                        out,
802                        "{}interface_method_sig {}({}{}{}) -> {}",
803                        unchecked_str,
804                        self.interner.resolve(name),
805                        recv,
806                        if params.is_empty() { "" } else { ", " },
807                        params_str.join(", "),
808                        self.interner.resolve(return_type)
809                    )
810                    .unwrap();
811                }
812
813                // Derives (ADR-0058)
814                InstData::DeriveDecl {
815                    name,
816                    methods_start,
817                    methods_len,
818                } => {
819                    let name_str = self.interner.resolve(name);
820                    let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
821                    let method_refs: Vec<String> =
822                        methods.iter().map(|m| format!("{}", m)).collect();
823                    writeln!(out, "derive {} {{ {} }}", name_str, method_refs.join(", ")).unwrap();
824                }
825
826                // Comptime block
827                InstData::Comptime { expr } => {
828                    writeln!(out, "comptime {{ {} }}", expr).unwrap();
829                }
830
831                // Comptime unroll for
832                InstData::ComptimeUnrollFor {
833                    binding,
834                    iterable,
835                    body,
836                } => writeln!(
837                    out,
838                    "comptime_unroll for {} in {}, {}",
839                    self.interner.resolve(binding),
840                    iterable,
841                    body
842                )
843                .unwrap(),
844
845                // Checked block
846                InstData::Checked { expr } => {
847                    writeln!(out, "checked {{ {} }}", expr).unwrap();
848                }
849
850                // Type constant
851                InstData::TypeConst { type_name } => {
852                    let name = self.interner.resolve(type_name);
853                    writeln!(out, "type {}", name).unwrap();
854                }
855
856                // Anonymous struct type
857                InstData::AnonStructType {
858                    fields_start,
859                    fields_len,
860                    methods_start,
861                    methods_len,
862                    ..
863                } => {
864                    write!(out, "struct {{ ").unwrap();
865                    let fields = self.rir.get_field_decls(*fields_start, *fields_len);
866                    for (i, (name, ty)) in fields.iter().enumerate() {
867                        if i > 0 {
868                            write!(out, ", ").unwrap();
869                        }
870                        let name_str = self.interner.resolve(name);
871                        let ty_str = self.interner.resolve(ty);
872                        write!(out, "{}: {}", name_str, ty_str).unwrap();
873                    }
874                    // Print methods if any
875                    if *methods_len > 0 {
876                        let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
877                        let methods_str: Vec<String> =
878                            methods.iter().map(|m| format!("{}", m)).collect();
879                        if !fields.is_empty() {
880                            write!(out, ", ").unwrap();
881                        }
882                        write!(out, "methods: [{}]", methods_str.join(", ")).unwrap();
883                    }
884                    writeln!(out, " }}").unwrap();
885                }
886
887                // Anonymous enum type
888                InstData::AnonEnumType {
889                    variants_start,
890                    variants_len,
891                    methods_start,
892                    methods_len,
893                    ..
894                } => {
895                    write!(out, "enum {{ ").unwrap();
896                    let variants = self
897                        .rir
898                        .get_enum_variant_decls(*variants_start, *variants_len);
899                    let variants_str: Vec<String> = variants
900                        .iter()
901                        .map(|(v, field_types, field_names)| {
902                            let vname = self.interner.resolve(v).to_string();
903                            if field_types.is_empty() {
904                                vname
905                            } else if field_names.is_empty() {
906                                let field_strs: Vec<&str> = field_types
907                                    .iter()
908                                    .map(|f| self.interner.resolve(f))
909                                    .collect();
910                                format!("{}({})", vname, field_strs.join(", "))
911                            } else {
912                                let field_strs: Vec<String> = field_names
913                                    .iter()
914                                    .zip(field_types.iter())
915                                    .map(|(n, t)| {
916                                        format!(
917                                            "{}: {}",
918                                            self.interner.resolve(n),
919                                            self.interner.resolve(t)
920                                        )
921                                    })
922                                    .collect();
923                                format!("{} {{ {} }}", vname, field_strs.join(", "))
924                            }
925                        })
926                        .collect();
927                    write!(out, "{}", variants_str.join(", ")).unwrap();
928                    if *methods_len > 0 {
929                        let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
930                        let methods_str: Vec<String> =
931                            methods.iter().map(|m| format!("{}", m)).collect();
932                        if !variants_str.is_empty() {
933                            write!(out, ", ").unwrap();
934                        }
935                        write!(out, "methods: [{}]", methods_str.join(", ")).unwrap();
936                    }
937                    writeln!(out, " }}").unwrap();
938                }
939                // Tuple lowering (ADR-0048) — printed with the same tuple-literal syntax
940                InstData::TupleInit {
941                    elems_start,
942                    elems_len,
943                } => {
944                    let elems = self.rir.get_inst_refs(*elems_start, *elems_len);
945                    let elems_str: Vec<String> = elems.iter().map(|e| format!("{}", e)).collect();
946                    if elems.len() == 1 {
947                        writeln!(out, "tuple ({},)", elems_str[0]).unwrap();
948                    } else {
949                        writeln!(out, "tuple ({})", elems_str.join(", ")).unwrap();
950                    }
951                }
952                // Anonymous function value (ADR-0055) — prints as "anon_fn call=<method ref>"
953                InstData::AnonFnValue { method } => {
954                    writeln!(out, "anon_fn call={}", method).unwrap();
955                }
956                // Anonymous interface type (ADR-0057)
957                InstData::AnonInterfaceType {
958                    methods_start,
959                    methods_len,
960                } => {
961                    let methods = self.rir.get_inst_refs(*methods_start, *methods_len);
962                    let refs: Vec<String> = methods.iter().map(|m| format!("{}", m)).collect();
963                    writeln!(out, "anon_interface_type {{ {} }}", refs.join(", ")).unwrap();
964                }
965            }
966        }
967        out
968    }
969}
970
971impl fmt::Display for RirPrinter<'_, '_> {
972    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
973        write!(f, "{}", self.render())
974    }
975}