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