Skip to main content

aver/ir/mir/
dump.rs

1//! Textual dump for Core MIR.
2//!
3//! Phase 2b of #252. Renders `MirProgram` / `MirFn` / `MirExpr` /
4//! `MirPattern` as a structured text view suitable for snapshot
5//! tests, pass reports, and the future `aver compile
6//! --emit-ir-after=mir` flag (the flag itself lands together with
7//! Phase 3's first lowering wave — there's no source-of-`MirProgram`
8//! until then).
9//!
10//! Format philosophy:
11//! - **Stable order**: functions sorted by `FnId`, fields by their
12//!   declared order, locals by introduction order. Snapshot tests
13//!   only stay green if every walker visits the same nodes in the
14//!   same sequence.
15//! - **Identity visible**: `FnId`, `TypeId`, `CtorId`, `LocalId`
16//!   all appear in the dump so a reviewer can tell whether the
17//!   lowerer wired references through the identity layer or fell
18//!   back to a string lookup.
19//! - **One concept per line**: each `Let`, each `Match` arm, each
20//!   constructor / record field gets its own line. Wide
21//!   expressions still wrap, but the structural skeleton stays
22//!   line-oriented so `diff` reads cleanly.
23//! - **No span noise by default**: source line numbers would
24//!   churn the snapshots whenever a fixture moves by one line.
25//!   When a future diagnostic consumer needs span output, it can
26//!   call into a richer formatter; the `Display` impl stays
27//!   span-free.
28
29use std::collections::BTreeMap;
30use std::fmt;
31
32use crate::ast::Spanned;
33
34use super::expr::{
35    MirBinOp, MirCall, MirCallee, MirConstruct, MirEffectAnnotation, MirExpr,
36    MirIndependentProduct, MirLet, MirMatch, MirMatchArm, MirPattern, MirProject, MirRecordCreate,
37    MirRecordField, MirRecordUpdate, MirStrPart, MirTailCall,
38};
39use super::program::{MirFn, MirParam, MirProgram};
40
41impl fmt::Display for MirProgram {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        writeln!(f, "MirProgram {{")?;
44        // Sort by FnId so dump order is independent of HashMap
45        // iteration order (which would otherwise drift across
46        // platforms + Rust versions).
47        let sorted: BTreeMap<_, _> = self.fns.iter().collect();
48        for (fn_id, mir_fn) in sorted {
49            writeln!(f, "  // FnId({})", fn_id.0)?;
50            write_fn(f, mir_fn, "  ")?;
51        }
52        if !self.modules.is_empty() {
53            writeln!(f, "  modules: [")?;
54            for m in &self.modules {
55                writeln!(f, "    ModuleId({})", m.0)?;
56            }
57            writeln!(f, "  ]")?;
58        }
59        writeln!(f, "}}")
60    }
61}
62
63impl fmt::Display for MirFn {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write_fn(f, self, "")
66    }
67}
68
69fn write_fn(f: &mut fmt::Formatter<'_>, mir_fn: &MirFn, indent: &str) -> fmt::Result {
70    write!(f, "{indent}fn {}(", mir_fn.name)?;
71    for (i, p) in mir_fn.params.iter().enumerate() {
72        if i > 0 {
73            write!(f, ", ")?;
74        }
75        write_param(f, p)?;
76    }
77    writeln!(f, ") -> {} {{", mir_fn.return_type)?;
78    if !mir_fn.effects.is_empty() {
79        write!(f, "{indent}  effects: [")?;
80        for (i, e) in mir_fn.effects.iter().enumerate() {
81            if i > 0 {
82                write!(f, ", ")?;
83            }
84            write_effect(f, e)?;
85        }
86        writeln!(f, "]")?;
87    }
88    write!(f, "{indent}  ")?;
89    write_expr(f, &mir_fn.body, &format!("{indent}  "))?;
90    writeln!(f)?;
91    writeln!(f, "{indent}}}")
92}
93
94fn write_param(f: &mut fmt::Formatter<'_>, p: &MirParam) -> fmt::Result {
95    write!(f, "{}{}: {}", p.local, p.name, p.ty)
96}
97
98fn write_effect(f: &mut fmt::Formatter<'_>, e: &MirEffectAnnotation) -> fmt::Result {
99    write!(f, "{}", e.name)
100}
101
102fn write_expr(f: &mut fmt::Formatter<'_>, expr: &Spanned<MirExpr>, indent: &str) -> fmt::Result {
103    match &expr.node {
104        MirExpr::Literal(lit) => write!(f, "{:?}", lit.node),
105        MirExpr::Local(local) => write!(f, "{}", local.node),
106        MirExpr::FnValue(name) => write!(f, "fn_value({name})"),
107        MirExpr::Let(spanned) => write_let(f, &spanned.node, indent),
108        MirExpr::Call(spanned) => write_call(f, &spanned.node, indent),
109        MirExpr::TailCall(spanned) => write_tail_call(f, &spanned.node, indent),
110        MirExpr::BinOp(spanned) => write_bin_op(f, &spanned.node, indent),
111        MirExpr::Neg(inner) => {
112            write!(f, "-(")?;
113            write_expr(f, inner, indent)?;
114            write!(f, ")")
115        }
116        MirExpr::Match(spanned) => write_match(f, &spanned.node, indent),
117        MirExpr::IfThenElse(spanned) => {
118            let ite = &spanned.node;
119            write!(f, "if ")?;
120            write_expr(f, &ite.cond, indent)?;
121            write!(f, " then ")?;
122            write_expr(f, &ite.then_branch, indent)?;
123            write!(f, " else ")?;
124            write_expr(f, &ite.else_branch, indent)
125        }
126        MirExpr::Construct(spanned) => write_construct(f, &spanned.node, indent),
127        MirExpr::RecordCreate(spanned) => write_record_create(f, &spanned.node, indent),
128        MirExpr::RecordUpdate(spanned) => write_record_update(f, &spanned.node, indent),
129        MirExpr::Project(spanned) => write_project(f, &spanned.node, indent),
130        MirExpr::Try(inner) => {
131            write_expr(f, inner, indent)?;
132            write!(f, "?")
133        }
134        MirExpr::List(items) => write_list_or_tuple(f, "List", items, indent),
135        MirExpr::Tuple(items) => write_list_or_tuple(f, "Tuple", items, indent),
136        MirExpr::MapLiteral(pairs) => write_map_literal(f, pairs, indent),
137        MirExpr::InterpolatedStr(parts) => write_interp_str(f, parts, indent),
138        MirExpr::IndependentProduct(spanned) => write_independent_product(f, &spanned.node, indent),
139        MirExpr::Return(inner) => {
140            write!(f, "return ")?;
141            write_expr(f, inner, indent)
142        }
143        // ETAP-2 representation boundaries (only present on the Rust
144        // codegen clone after `bare_i64::rewrite_for_rust`).
145        MirExpr::Box(inner) => {
146            write!(f, "box(")?;
147            write_expr(f, inner, indent)?;
148            write!(f, ")")
149        }
150        MirExpr::Unbox(inner) => {
151            write!(f, "unbox(")?;
152            write_expr(f, inner, indent)?;
153            write!(f, ")")
154        }
155    }
156}
157
158fn write_let(f: &mut fmt::Formatter<'_>, let_node: &MirLet, indent: &str) -> fmt::Result {
159    writeln!(f, "let {} =", let_node.binding)?;
160    let inner = format!("{indent}  ");
161    write!(f, "{inner}")?;
162    write_expr(f, &let_node.value, &inner)?;
163    writeln!(f)?;
164    write!(f, "{indent}in ")?;
165    write_expr(f, &let_node.body, indent)
166}
167
168fn write_call(f: &mut fmt::Formatter<'_>, call: &MirCall, indent: &str) -> fmt::Result {
169    match &call.callee {
170        MirCallee::Fn(id) => write!(f, "FnId({}).call(", id.0)?,
171        MirCallee::Builtin(id) => write!(f, "Builtin(#{}).call(", id.0)?,
172        MirCallee::Intrinsic(i) => write!(f, "Intrinsic({i:?}).call(")?,
173        MirCallee::LocalSlot { slot, name, .. } => write!(f, "slot({slot}, {name}).call(")?,
174    }
175    write_args(f, &call.args, indent)?;
176    write!(f, ")")
177}
178
179fn write_tail_call(f: &mut fmt::Formatter<'_>, tc: &MirTailCall, indent: &str) -> fmt::Result {
180    write!(f, "tail FnId({}).call(", tc.target.0)?;
181    write_args(f, &tc.args, indent)?;
182    write!(f, ")")
183}
184
185fn write_args(f: &mut fmt::Formatter<'_>, args: &[Spanned<MirExpr>], indent: &str) -> fmt::Result {
186    for (i, a) in args.iter().enumerate() {
187        if i > 0 {
188            write!(f, ", ")?;
189        }
190        write_expr(f, a, indent)?;
191    }
192    Ok(())
193}
194
195fn write_bin_op(f: &mut fmt::Formatter<'_>, op: &MirBinOp, indent: &str) -> fmt::Result {
196    write!(f, "(")?;
197    write_expr(f, &op.lhs, indent)?;
198    write!(f, " {:?} ", op.op)?;
199    write_expr(f, &op.rhs, indent)?;
200    write!(f, ")")
201}
202
203fn write_match(f: &mut fmt::Formatter<'_>, m: &MirMatch, indent: &str) -> fmt::Result {
204    write!(f, "match ")?;
205    write_expr(f, &m.subject, indent)?;
206    writeln!(f, " {{")?;
207    let arm_indent = format!("{indent}  ");
208    for arm in &m.arms {
209        write_arm(f, arm, &arm_indent)?;
210    }
211    write!(f, "{indent}}}")
212}
213
214fn write_arm(f: &mut fmt::Formatter<'_>, arm: &MirMatchArm, indent: &str) -> fmt::Result {
215    write!(f, "{indent}")?;
216    write_pattern(f, &arm.pattern)?;
217    write!(f, " => ")?;
218    write_expr(f, &arm.body, indent)?;
219    writeln!(f, ",")
220}
221
222fn write_pattern(f: &mut fmt::Formatter<'_>, p: &MirPattern) -> fmt::Result {
223    match p {
224        MirPattern::Wildcard => write!(f, "_"),
225        MirPattern::Literal(lit) => write!(f, "{lit:?}"),
226        MirPattern::Bind(local, _name) => write!(f, "{local}"),
227        MirPattern::EmptyList => write!(f, "[]"),
228        MirPattern::Cons { head, tail, .. } => write!(f, "[{head}, ..{tail}]"),
229        MirPattern::Tuple(items) => {
230            write!(f, "(")?;
231            for (i, sub) in items.iter().enumerate() {
232                if i > 0 {
233                    write!(f, ", ")?;
234                }
235                write_pattern(f, sub)?;
236            }
237            write!(f, ")")
238        }
239        MirPattern::Ctor { ctor, bindings, .. } => {
240            write_ctor(f, *ctor)?;
241            write!(f, "(")?;
242            for (i, b) in bindings.iter().enumerate() {
243                if i > 0 {
244                    write!(f, ", ")?;
245                }
246                write!(f, "{b}")?;
247            }
248            write!(f, ")")
249        }
250    }
251}
252
253fn write_construct(f: &mut fmt::Formatter<'_>, c: &MirConstruct, indent: &str) -> fmt::Result {
254    write_ctor(f, c.ctor)?;
255    write!(f, "(")?;
256    write_args(f, &c.args, indent)?;
257    write!(f, ")")
258}
259
260fn write_ctor(f: &mut fmt::Formatter<'_>, ctor: super::MirCtor) -> fmt::Result {
261    match ctor {
262        super::MirCtor::User(id) => write!(f, "CtorId({})", id.0),
263        super::MirCtor::Builtin(b) => match b {
264            super::BuiltinCtor::ResultOk => write!(f, "Result.Ok"),
265            super::BuiltinCtor::ResultErr => write!(f, "Result.Err"),
266            super::BuiltinCtor::OptionSome => write!(f, "Option.Some"),
267            super::BuiltinCtor::OptionNone => write!(f, "Option.None"),
268        },
269    }
270}
271
272fn write_record_create(
273    f: &mut fmt::Formatter<'_>,
274    r: &MirRecordCreate,
275    indent: &str,
276) -> fmt::Result {
277    match r.type_id {
278        Some(id) => write!(f, "TypeId({}) {{ ", id.0)?,
279        None => write!(f, "{} {{ ", r.type_name)?,
280    }
281    write_fields(f, &r.fields, indent)?;
282    write!(f, " }}")
283}
284
285fn write_record_update(
286    f: &mut fmt::Formatter<'_>,
287    r: &MirRecordUpdate,
288    indent: &str,
289) -> fmt::Result {
290    match r.type_id {
291        Some(id) => write!(f, "TypeId({}).update(", id.0)?,
292        None => write!(f, "{}.update(", r.type_name)?,
293    }
294    write_expr(f, &r.base, indent)?;
295    write!(f, ", ")?;
296    write_fields(f, &r.updates, indent)?;
297    write!(f, ")")
298}
299
300fn write_fields(
301    f: &mut fmt::Formatter<'_>,
302    fields: &[MirRecordField],
303    indent: &str,
304) -> fmt::Result {
305    for (i, field) in fields.iter().enumerate() {
306        if i > 0 {
307            write!(f, ", ")?;
308        }
309        write!(f, "{} = ", field.name)?;
310        write_expr(f, &field.value, indent)?;
311    }
312    Ok(())
313}
314
315fn write_project(f: &mut fmt::Formatter<'_>, p: &MirProject, indent: &str) -> fmt::Result {
316    write_expr(f, &p.base, indent)?;
317    write!(f, ".{}", p.field)
318}
319
320fn write_list_or_tuple(
321    f: &mut fmt::Formatter<'_>,
322    tag: &str,
323    items: &[Spanned<MirExpr>],
324    indent: &str,
325) -> fmt::Result {
326    write!(f, "{tag}[")?;
327    write_args(f, items, indent)?;
328    write!(f, "]")
329}
330
331fn write_map_literal(
332    f: &mut fmt::Formatter<'_>,
333    pairs: &[(Spanned<MirExpr>, Spanned<MirExpr>)],
334    indent: &str,
335) -> fmt::Result {
336    write!(f, "Map{{")?;
337    for (i, (k, v)) in pairs.iter().enumerate() {
338        if i > 0 {
339            write!(f, ", ")?;
340        }
341        write_expr(f, k, indent)?;
342        write!(f, " => ")?;
343        write_expr(f, v, indent)?;
344    }
345    write!(f, "}}")
346}
347
348fn write_interp_str(f: &mut fmt::Formatter<'_>, parts: &[MirStrPart], indent: &str) -> fmt::Result {
349    write!(f, "\"")?;
350    for part in parts {
351        match part {
352            MirStrPart::Literal(s) => write!(f, "{}", s.replace('"', "\\\""))?,
353            MirStrPart::Expr(e) => {
354                write!(f, "{{")?;
355                write_expr(f, e, indent)?;
356                write!(f, "}}")?;
357            }
358        }
359    }
360    write!(f, "\"")
361}
362
363fn write_independent_product(
364    f: &mut fmt::Formatter<'_>,
365    ip: &MirIndependentProduct,
366    indent: &str,
367) -> fmt::Result {
368    write!(f, "(")?;
369    write_args(f, &ip.items, indent)?;
370    if ip.unwrap_results {
371        write!(f, ")?!")
372    } else {
373        write!(f, ")!")
374    }
375}
376
377// `LocalId`'s `Display` impl lives in `program.rs` — search for it
378// there. The format (`%N`) is shared by both this dump and any
379// future pass-report code that consumes the same IDs.