Skip to main content

hir_ty/mir/
pretty.rs

1//! A pretty-printer for MIR.
2
3use std::{
4    fmt::{Debug, Display, Write},
5    mem,
6};
7
8use hir_def::{
9    HasModule, VariantId,
10    expr_store::ExpressionStore,
11    hir::BindingId,
12    signatures::{ConstSignature, EnumSignature, FunctionSignature, StaticSignature},
13};
14use hir_expand::{Lookup, name::Name};
15use la_arena::ArenaMap;
16use rustc_type_ir::inherent::IntoKind;
17
18use crate::{
19    InferBodyId,
20    db::{HirDatabase, InternedClosureId},
21    display::{ClosureStyle, DisplayTarget, HirDisplay},
22    mir::{PlaceElem, PlaceTy, ProjectionElem, StatementKind, TerminatorKind},
23    next_solver::{DbInterner, TyKind, infer::DbInternerInferExt},
24};
25
26use super::{
27    AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, MutBorrowKind, Operand, OperandKind,
28    Place, Rvalue, UnOp,
29};
30
31macro_rules! w {
32    ($dst:expr, $($arg:tt)*) => {
33        { let _ = write!($dst, $($arg)*); }
34    };
35}
36
37macro_rules! wln {
38    ($dst:expr) => {
39        { let _ = writeln!($dst); }
40    };
41    ($dst:expr, $($arg:tt)*) => {
42        { let _ = writeln!($dst, $($arg)*); }
43    };
44}
45
46impl MirBody<'_> {
47    pub fn pretty_print(&self, db: &dyn HirDatabase, display_target: DisplayTarget) -> String {
48        let hir_body = ExpressionStore::of(db, self.owner.expression_store_owner(db));
49        let mut ctx = MirPrettyCtx::new(self, hir_body, db, display_target);
50        ctx.for_body(|this| match ctx.body.owner {
51            InferBodyId::DefWithBodyId(hir_def::DefWithBodyId::FunctionId(id)) => {
52                let data = FunctionSignature::of(db, id);
53                w!(this, "fn {}() ", data.name.display(db, this.display_target.edition));
54            }
55            InferBodyId::DefWithBodyId(hir_def::DefWithBodyId::StaticId(id)) => {
56                let data = StaticSignature::of(db, id);
57                w!(this, "static {}: _ = ", data.name.display(db, this.display_target.edition));
58            }
59            InferBodyId::DefWithBodyId(hir_def::DefWithBodyId::ConstId(id)) => {
60                let data = ConstSignature::of(db, id);
61                w!(
62                    this,
63                    "const {}: _ = ",
64                    data.name
65                        .as_ref()
66                        .unwrap_or(&Name::missing())
67                        .display(db, this.display_target.edition)
68                );
69            }
70            InferBodyId::DefWithBodyId(hir_def::DefWithBodyId::VariantId(id)) => {
71                let loc = id.lookup(db);
72                let edition = this.display_target.edition;
73                w!(
74                    this,
75                    "enum {}::{} = ",
76                    EnumSignature::of(db, loc.parent).name.display(db, edition),
77                    loc.parent
78                        .enum_variants(db)
79                        .variant_name_by_id(id)
80                        .unwrap()
81                        .display(db, edition),
82                )
83            }
84            InferBodyId::AnonConstId(_) => w!(this, "{{const}}"),
85        });
86        ctx.result
87    }
88
89    // String with lines is rendered poorly in `dbg` macros, which I use very much, so this
90    // function exists to solve that.
91    pub fn dbg(&self, db: &dyn HirDatabase, display_target: DisplayTarget) -> impl Debug {
92        struct StringDbg(String);
93        impl Debug for StringDbg {
94            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95                f.write_str(&self.0)
96            }
97        }
98        StringDbg(self.pretty_print(db, display_target))
99    }
100}
101
102struct MirPrettyCtx<'a, 'db> {
103    body: &'a MirBody<'db>,
104    hir_body: &'a ExpressionStore,
105    db: &'db dyn HirDatabase,
106    result: String,
107    indent: String,
108    local_to_binding: ArenaMap<LocalId, BindingId>,
109    display_target: DisplayTarget,
110}
111
112impl Write for MirPrettyCtx<'_, '_> {
113    fn write_str(&mut self, s: &str) -> std::fmt::Result {
114        let mut it = s.split('\n'); // note: `.lines()` is wrong here
115        self.write(it.next().unwrap_or_default());
116        for line in it {
117            self.write_line();
118            self.write(line);
119        }
120        Ok(())
121    }
122}
123
124enum LocalName {
125    Unknown(LocalId),
126    Binding(Name, LocalId),
127}
128
129impl<'db> HirDisplay<'db> for LocalName {
130    fn hir_fmt(
131        &self,
132        f: &mut crate::display::HirFormatter<'_, 'db>,
133    ) -> Result<(), crate::display::HirDisplayError> {
134        match self {
135            LocalName::Unknown(l) => write!(f, "_{}", u32::from(l.into_raw())),
136            LocalName::Binding(n, l) => {
137                write!(f, "{}_{}", n.display(f.db, f.edition()), u32::from(l.into_raw()))
138            }
139        }
140    }
141}
142
143impl<'a, 'db> MirPrettyCtx<'a, 'db> {
144    fn for_body(&mut self, name: impl FnOnce(&mut MirPrettyCtx<'_, 'db>)) {
145        name(self);
146        self.with_block(|this| {
147            this.locals();
148            wln!(this);
149            this.blocks();
150        });
151        for &closure in &self.body.closures {
152            self.for_closure(closure);
153        }
154    }
155
156    fn for_closure(&mut self, closure: InternedClosureId<'db>) {
157        let body = match self.db.mir_body_for_closure(closure) {
158            Ok(it) => it,
159            Err(e) => {
160                wln!(self, "// error in {closure:?}: {e:?}");
161                return;
162            }
163        };
164        let result = mem::take(&mut self.result);
165        let indent = mem::take(&mut self.indent);
166        let mut ctx = MirPrettyCtx {
167            body,
168            local_to_binding: body.local_to_binding_map(),
169            result,
170            indent,
171            ..*self
172        };
173        ctx.for_body(|this| wln!(this, "// Closure: {:?}", closure));
174        self.result = ctx.result;
175        self.indent = ctx.indent;
176    }
177
178    fn with_block(&mut self, f: impl FnOnce(&mut MirPrettyCtx<'_, 'db>)) {
179        self.indent += "    ";
180        wln!(self, "{{");
181        f(self);
182        for _ in 0..4 {
183            self.result.pop();
184            self.indent.pop();
185        }
186        wln!(self, "}}");
187    }
188
189    fn new(
190        body: &'a MirBody<'db>,
191        hir_body: &'a ExpressionStore,
192        db: &'db dyn HirDatabase,
193        display_target: DisplayTarget,
194    ) -> Self {
195        let local_to_binding = body.local_to_binding_map();
196        MirPrettyCtx {
197            body,
198            db,
199            result: String::new(),
200            indent: String::new(),
201            local_to_binding,
202            hir_body,
203            display_target,
204        }
205    }
206
207    fn write_line(&mut self) {
208        self.result.push('\n');
209        self.result += &self.indent;
210    }
211
212    fn write(&mut self, line: &str) {
213        self.result += line;
214    }
215
216    fn locals(&mut self) {
217        for (id, local) in self.body.locals.iter() {
218            wln!(
219                self,
220                "let {}: {};",
221                self.local_name(id).display_test(self.db, self.display_target),
222                self.hir_display(&local.ty.as_ref())
223            );
224        }
225    }
226
227    fn local_name(&self, local: LocalId) -> LocalName {
228        match self.local_to_binding.get(local) {
229            Some(b) => LocalName::Binding(self.hir_body[*b].name.clone(), local),
230            None => LocalName::Unknown(local),
231        }
232    }
233
234    fn basic_block_id(&self, basic_block_id: BasicBlockId) -> String {
235        format!("'bb{}", u32::from(basic_block_id.into_raw()))
236    }
237
238    fn blocks(&mut self) {
239        for (id, block) in self.body.basic_blocks.iter() {
240            wln!(self);
241            w!(self, "{}: ", self.basic_block_id(id));
242            self.with_block(|this| {
243                for statement in &block.statements {
244                    match &statement.kind {
245                        StatementKind::Assign(l, r) => {
246                            this.place(l);
247                            w!(this, " = ");
248                            this.rvalue(r);
249                            wln!(this, ";");
250                        }
251                        StatementKind::StorageDead(p) => {
252                            wln!(
253                                this,
254                                "StorageDead({})",
255                                this.local_name(*p).display_test(this.db, this.display_target)
256                            );
257                        }
258                        StatementKind::StorageLive(p) => {
259                            wln!(
260                                this,
261                                "StorageLive({})",
262                                this.local_name(*p).display_test(this.db, this.display_target)
263                            );
264                        }
265                        StatementKind::Deinit(p) => {
266                            w!(this, "Deinit(");
267                            this.place(p);
268                            wln!(this, ");");
269                        }
270                        StatementKind::FakeRead(p) => {
271                            w!(this, "FakeRead(");
272                            this.place(p);
273                            wln!(this, ");");
274                        }
275                        StatementKind::Nop => wln!(this, "Nop;"),
276                    }
277                }
278                match &block.terminator {
279                    Some(terminator) => match &terminator.kind {
280                        TerminatorKind::Goto { target } => {
281                            wln!(this, "goto 'bb{};", u32::from(target.into_raw()))
282                        }
283                        TerminatorKind::SwitchInt { discr, targets } => {
284                            w!(this, "switch ");
285                            this.operand(discr);
286                            w!(this, " ");
287                            this.with_block(|this| {
288                                for (c, b) in targets.iter() {
289                                    wln!(this, "{c} => {},", this.basic_block_id(b));
290                                }
291                                wln!(this, "_ => {},", this.basic_block_id(targets.otherwise()));
292                            });
293                        }
294                        TerminatorKind::Call { func, args, destination, target, .. } => {
295                            w!(this, "Call ");
296                            this.with_block(|this| {
297                                w!(this, "func: ");
298                                this.operand(func);
299                                wln!(this, ",");
300                                w!(this, "args: [");
301                                this.operand_list(args);
302                                wln!(this, "],");
303                                w!(this, "destination: ");
304                                this.place(destination);
305                                wln!(this, ",");
306                                w!(this, "target: ");
307                                match target {
308                                    Some(t) => w!(this, "{}", this.basic_block_id(*t)),
309                                    None => w!(this, "<unreachable>"),
310                                }
311                                wln!(this, ",");
312                            });
313                        }
314                        _ => wln!(this, "{:?};", terminator),
315                    },
316                    None => wln!(this, "<no-terminator>;"),
317                }
318            })
319        }
320    }
321
322    fn place(&mut self, p: &Place) {
323        fn f<'db>(this: &mut MirPrettyCtx<'_, 'db>, local: LocalId, projections: &[PlaceElem]) {
324            let Some((last, head)) = projections.split_last() else {
325                // no projection
326                w!(this, "{}", this.local_name(local).display_test(this.db, this.display_target));
327                return;
328            };
329            match last {
330                ProjectionElem::Deref => {
331                    w!(this, "(*");
332                    f(this, local, head);
333                    w!(this, ")");
334                }
335                ProjectionElem::Downcast(variant_id) => match variant_id {
336                    hir_def::VariantId::EnumVariantId(e) => {
337                        w!(this, "(");
338                        f(this, local, head);
339                        let loc = e.lookup(this.db);
340                        w!(this, " as {})", loc.name.display(this.db, this.display_target.edition),);
341                    }
342                    _ => {
343                        f(this, local, head);
344                        w!(this, ".{:?}", last);
345                    }
346                },
347                ProjectionElem::Field(field) => {
348                    f(this, local, head);
349
350                    // we need to get the base type to decide how to display the field / get the field name
351                    let infcx = DbInterner::new_with(this.db, this.body.owner.krate(this.db))
352                        .infer_ctxt()
353                        .build(rustc_type_ir::TypingMode::PostAnalysis);
354                    let env = this.db.trait_environment(this.body.owner.generic_def(this.db));
355                    let place_ty = PlaceTy::from_ty(this.body.locals[local].ty.as_ref())
356                        .multi_projection_ty(&infcx, env, projections);
357                    if let Some(variant_id) = place_ty.variant_id {
358                        let variant_fields = variant_id.fields(this.db);
359                        w!(
360                            this,
361                            ".{}",
362                            variant_fields.fields()[field.to_local_field_id()]
363                                .name
364                                .display(this.db, this.display_target.edition)
365                        );
366                    } else {
367                        match place_ty.ty.kind() {
368                            TyKind::Adt(adt_def, _) if !adt_def.is_enum() => {
369                                let variant_id =
370                                    VariantId::from_non_enum(adt_def.def_id()).unwrap();
371                                let fields = variant_id.fields(this.db);
372                                w!(
373                                    this,
374                                    ".{}",
375                                    fields.fields()[field.to_local_field_id()]
376                                        .name
377                                        .display(this.db, this.display_target.edition)
378                                );
379                            }
380                            TyKind::Tuple(_) | TyKind::Closure(..) => w!(this, ".{}", field.0),
381                            _ => {
382                                w!(this, ".{:?}", last);
383                            }
384                        }
385                    };
386                }
387                ProjectionElem::Index(l) => {
388                    f(this, local, head);
389                    w!(
390                        this,
391                        "[{}]",
392                        this.local_name(*l).display_test(this.db, this.display_target)
393                    );
394                }
395                it => {
396                    f(this, local, head);
397                    w!(this, ".{:?}", it);
398                }
399            }
400        }
401        f(self, p.local, p.projection.lookup());
402    }
403
404    fn operand(&mut self, r: &Operand) {
405        match &r.kind {
406            OperandKind::Copy(p) | OperandKind::Move(p) => {
407                // MIR at the time of writing doesn't have difference between move and copy, so we show them
408                // equally. Feel free to change it.
409                self.place(p);
410            }
411            OperandKind::Constant { konst, .. } => {
412                w!(self, "Const({})", self.hir_display(&konst.as_ref()))
413            }
414            OperandKind::Static(s) => w!(self, "Static({:?})", s),
415            OperandKind::Allocation { allocation } => {
416                w!(self, "Allocation({})", self.hir_display(&allocation.as_ref()))
417            }
418        }
419    }
420
421    fn rvalue(&mut self, r: &Rvalue) {
422        match r {
423            Rvalue::Use(op) => self.operand(op),
424            Rvalue::Ref(r, p) => {
425                match r {
426                    BorrowKind::Shared => w!(self, "&"),
427                    BorrowKind::Shallow => w!(self, "&shallow "),
428                    BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture } => w!(self, "&uniq "),
429                    BorrowKind::Mut {
430                        kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow,
431                    } => w!(self, "&mut "),
432                }
433                self.place(p);
434            }
435            Rvalue::Aggregate(AggregateKind::Tuple(_), it) => {
436                w!(self, "(");
437                self.operand_list(it);
438                w!(self, ")");
439            }
440            Rvalue::Aggregate(AggregateKind::Array(_), it) => {
441                w!(self, "[");
442                self.operand_list(it);
443                w!(self, "]");
444            }
445            Rvalue::Repeat(op, len) => {
446                w!(self, "[");
447                self.operand(op);
448                w!(self, "; {}]", len.as_ref().display_test(self.db, self.display_target));
449            }
450            Rvalue::Aggregate(AggregateKind::Adt(_, _), it) => {
451                w!(self, "Adt(");
452                self.operand_list(it);
453                w!(self, ")");
454            }
455            Rvalue::Aggregate(AggregateKind::Closure(_), it) => {
456                w!(self, "Closure(");
457                self.operand_list(it);
458                w!(self, ")");
459            }
460            Rvalue::Aggregate(AggregateKind::Union(_, _), it) => {
461                w!(self, "Union(");
462                self.operand_list(it);
463                w!(self, ")");
464            }
465            Rvalue::Len(p) => {
466                w!(self, "Len(");
467                self.place(p);
468                w!(self, ")");
469            }
470            Rvalue::Cast(ck, op, ty) => {
471                w!(self, "Cast({ck:?}, ");
472                self.operand(op);
473                w!(self, ", {})", self.hir_display(&ty.as_ref()));
474            }
475            Rvalue::CheckedBinaryOp(b, o1, o2) => {
476                self.operand(o1);
477                w!(self, " {b} ");
478                self.operand(o2);
479            }
480            Rvalue::UnaryOp(u, o) => {
481                let u = match u {
482                    UnOp::Not => "!",
483                    UnOp::Neg => "-",
484                };
485                w!(self, "{u} ");
486                self.operand(o);
487            }
488            Rvalue::Discriminant(p) => {
489                w!(self, "Discriminant(");
490                self.place(p);
491                w!(self, ")");
492            }
493            Rvalue::ShallowInitBoxWithAlloc(_) => w!(self, "ShallowInitBoxWithAlloc"),
494            Rvalue::ShallowInitBox(op, _) => {
495                w!(self, "ShallowInitBox(");
496                self.operand(op);
497                w!(self, ")");
498            }
499            Rvalue::CopyForDeref(p) => {
500                w!(self, "CopyForDeref(");
501                self.place(p);
502                w!(self, ")");
503            }
504            Rvalue::ThreadLocalRef(n)
505            | Rvalue::AddressOf(n)
506            | Rvalue::BinaryOp(n)
507            | Rvalue::NullaryOp(n) => match *n {},
508        }
509    }
510
511    fn operand_list(&mut self, it: &[Operand]) {
512        let mut it = it.iter();
513        if let Some(first) = it.next() {
514            self.operand(first);
515            for op in it {
516                w!(self, ", ");
517                self.operand(op);
518            }
519        }
520    }
521
522    fn hir_display<'b, T: HirDisplay<'db>>(&self, ty: &'b T) -> impl Display + use<'a, 'b, 'db, T>
523    where
524        'db: 'b,
525    {
526        ty.display_test(self.db, self.display_target)
527            .with_closure_style(ClosureStyle::ClosureWithSubst)
528    }
529}