hir_ty/mir/
pretty.rs

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