Skip to main content

hir_ide/
mir_pretty.rs

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