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