1use 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 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'); 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 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 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}