Repository: rust-lang/rust-analyzer
Pull request #13024 (2022-08-15 11:55:02)

internal: Add an HIR pretty-printer
This improves the "View HIR" command by pretty-printing the HIR to make it much more readable.

Example function:

```rust
    fn newline(&mut self) {
        match self.buf.chars().rev().skip_while(|ch| *ch == ' ').next() {
            Some('\n') | None => {}
            _ => writeln!(self).unwrap(),
        }
    }
```

Previous output:

```
HIR expressions in the body of `newline`:
Idx::<Expr>(0): Path(Path { type_anchor: None, mod_path: ModPath { kind: Super(0), segments: [] }, generic_args: [] })
Idx::<Expr>(1): Field { expr: Idx::<Expr>(0), name: Name(Text("buf")) }
Idx::<Expr>(2): MethodCall { receiver: Idx::<Expr>(1), method_name: Name(Text("chars")), args: [], generic_args: None }
Idx::<Expr>(3): MethodCall { receiver: Idx::<Expr>(2), method_name: Name(Text("rev")), args: [], generic_args: None }
Idx::<Expr>(4): Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("ch"))] }, generic_args: [None] })
Idx::<Expr>(5): UnaryOp { expr: Idx::<Expr>(4), op: Deref }
Idx::<Expr>(6): Literal(Char(' '))
Idx::<Expr>(7): BinaryOp { lhs: Idx::<Expr>(5), rhs: Idx::<Expr>(6), op: Some(CmpOp(Eq { negated: false })) }
Idx::<Expr>(8): Closure { args: [Idx::<Pat>(1)], arg_types: [None], ret_type: None, body: Idx::<Expr>(7) }
Idx::<Expr>(9): MethodCall { receiver: Idx::<Expr>(3), method_name: Name(Text("skip_while")), args: [Idx::<Expr>(8)], generic_args: None }
Idx::<Expr>(10): MethodCall { receiver: Idx::<Expr>(9), method_name: Name(Text("next")), args: [], generic_args: None }
Idx::<Expr>(11): Literal(Char('\n'))
Idx::<Expr>(12): Block { id: BlockId(37), statements: [], tail: None, label: None }
Idx::<Expr>(13): Path(Path { type_anchor: None, mod_path: ModPath { kind: Super(0), segments: [] }, generic_args: [] })
Idx::<Expr>(14): Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("std")), Name(Text("fmt")), Name(Text("Arguments")), Name(Text("new_v1"))] }, generic_args: [None, None, None, None] })
Idx::<Expr>(15): Array(ElementList { elements: [], is_assignee_expr: false })
Idx::<Expr>(16): Ref { expr: Idx::<Expr>(15), rawness: Ref, mutability: Shared }
Idx::<Expr>(17): Array(ElementList { elements: [], is_assignee_expr: false })
Idx::<Expr>(18): Ref { expr: Idx::<Expr>(17), rawness: Ref, mutability: Shared }
Idx::<Expr>(19): Call { callee: Idx::<Expr>(14), args: [Idx::<Expr>(16), Idx::<Expr>(18)], is_assignee_expr: false }
Idx::<Expr>(20): MethodCall { receiver: Idx::<Expr>(13), method_name: Name(Text("write_fmt")), args: [Idx::<Expr>(19)], generic_args: None }
Idx::<Expr>(21): MacroStmts { statements: [], tail: Some(Idx::<Expr>(20)) }
Idx::<Expr>(22): MethodCall { receiver: Idx::<Expr>(21), method_name: Name(Text("unwrap")), args: [], generic_args: None }
Idx::<Expr>(23): Match { expr: Idx::<Expr>(10), arms: [MatchArm { pat: Idx::<Pat>(5), guard: None, expr: Idx::<Expr>(12) }, MatchArm { pat: Idx::<Pat>(6), guard: None, expr: Idx::<Expr>(22) }] }
Idx::<Expr>(24): Block { id: BlockId(36), statements: [], tail: Some(Idx::<Expr>(23)), label: None }
```

Output after this PR:

```rust
fn newline(…) {
    match self.buf.chars().rev().skip_while(
        |ch| (*ch) == (' '),
    ).next() {
        Some('\n') | None => {},
        _ => { // macro statements
            self.write_fmt(
                std::fmt::Arguments::new_v1(
                    &[],
                    &[],
                ),
            )
        }.unwrap(),
    }
}
```

It also works for consts and statics now.

This should make debugging HIR-lowering related issues like https://github.com/rust-lang/rust-analyzer/issues/12940 much easier.


Patch:
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs
index 080a307b1f..1d818d9626 100644
--- a/crates/hir-def/src/body.rs
+++ b/crates/hir-def/src/body.rs
@@ -4,6 +4,7 @@ mod lower;
 #[cfg(test)]
 mod tests;
 pub mod scope;
+mod pretty;
 
 use std::{ops::Index, sync::Arc};
 
@@ -352,6 +353,10 @@ impl Body {
         }
     }
 
+    pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String {
+        pretty::print_body_hir(db, self, owner)
+    }
+
     fn new(
         db: &dyn DefDatabase,
         expander: Expander,
diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs
new file mode 100644
index 0000000000..ddd476efe5
--- /dev/null
+++ b/crates/hir-def/src/body/pretty.rs
@@ -0,0 +1,621 @@
+//! A pretty-printer for HIR.
+
+use std::fmt::{self, Write};
+
+use crate::{
+    expr::{Array, BindingAnnotation, Literal, Statement},
+    pretty::{print_generic_args, print_path, print_type_ref},
+    type_ref::TypeRef,
+};
+
+use super::*;
+
+pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBodyId) -> String {
+    let needs_semi;
+    let header = match owner {
+        DefWithBodyId::FunctionId(it) => {
+            needs_semi = false;
+            let item_tree_id = it.lookup(db).id;
+            format!("fn {}(…) ", item_tree_id.item_tree(db)[item_tree_id.value].name)
+        }
+        DefWithBodyId::StaticId(it) => {
+            needs_semi = true;
+            let item_tree_id = it.lookup(db).id;
+            format!("static {} = ", item_tree_id.item_tree(db)[item_tree_id.value].name)
+        }
+        DefWithBodyId::ConstId(it) => {
+            needs_semi = true;
+            let item_tree_id = it.lookup(db).id;
+            let name = match &item_tree_id.item_tree(db)[item_tree_id.value].name {
+                Some(name) => name.to_string(),
+                None => "_".to_string(),
+            };
+            format!("const {} = ", name)
+        }
+    };
+
+    let mut p = Printer { body, buf: header, indent_level: 0, needs_indent: false };
+    p.print_expr(body.body_expr);
+    if needs_semi {
+        p.buf.push(';');
+    }
+    p.buf
+}
+
+macro_rules! w {
+    ($dst:expr, $($arg:tt)*) => {
+        { let _ = write!($dst, $($arg)*); }
+    };
+}
+
+macro_rules! wln {
+    ($dst:expr) => {
+        { let _ = writeln!($dst); }
+    };
+    ($dst:expr, $($arg:tt)*) => {
+        { let _ = writeln!($dst, $($arg)*); }
+    };
+}
+
+struct Printer<'a> {
+    body: &'a Body,
+    buf: String,
+    indent_level: usize,
+    needs_indent: bool,
+}
+
+impl<'a> Write for Printer<'a> {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        for line in s.split_inclusive('\n') {
+            if self.needs_indent {
+                match self.buf.chars().rev().skip_while(|ch| *ch == ' ').next() {
+                    Some('\n') | None => {}
+                    _ => self.buf.push('\n'),
+                }
+                self.buf.push_str(&"    ".repeat(self.indent_level));
+                self.needs_indent = false;
+            }
+
+            self.buf.push_str(line);
+            self.needs_indent = line.ends_with('\n');
+        }
+
+        Ok(())
+    }
+}
+
+impl<'a> Printer<'a> {
+    fn indented(&mut self, f: impl FnOnce(&mut Self)) {
+        self.indent_level += 1;
+        wln!(self);
+        f(self);
+        self.indent_level -= 1;
+        self.buf = self.buf.trim_end_matches('\n').to_string();
+    }
+
+    fn whitespace(&mut self) {
+        match self.buf.chars().next_back() {
+            None | Some('\n' | ' ') => {}
+            _ => self.buf.push(' '),
+        }
+    }
+
+    fn newline(&mut self) {
+        match self.buf.chars().rev().skip_while(|ch| *ch == ' ').next() {
+            Some('\n') | None => {}
+            _ => writeln!(self).unwrap(),
+        }
+    }
+
+    fn print_expr(&mut self, expr: ExprId) {
+        let expr = &self.body[expr];
+
+        match expr {
+            Expr::Missing => w!(self, "�"),
+            Expr::Underscore => w!(self, "_"),
+            Expr::Path(path) => self.print_path(path),
+            Expr::If { condition, then_branch, else_branch } => {
+                w!(self, "if ");
+                self.print_expr(*condition);
+                w!(self, " ");
+                self.print_expr(*then_branch);
+                if let Some(els) = *else_branch {
+                    w!(self, " else ");
+                    self.print_expr(els);
+                }
+            }
+            Expr::Let { pat, expr } => {
+                w!(self, "let ");
+                self.print_pat(*pat);
+                w!(self, " = ");
+                self.print_expr(*expr);
+            }
+            Expr::Loop { body, label } => {
+                if let Some(lbl) = label {
+                    w!(self, "{}: ", self.body[*lbl].name);
+                }
+                w!(self, "loop ");
+                self.print_expr(*body);
+            }
+            Expr::While { condition, body, label } => {
+                if let Some(lbl) = label {
+                    w!(self, "{}: ", self.body[*lbl].name);
+                }
+                w!(self, "while ");
+                self.print_expr(*condition);
+                self.print_expr(*body);
+            }
+            Expr::For { iterable, pat, body, label } => {
+                if let Some(lbl) = label {
+                    w!(self, "{}: ", self.body[*lbl].name);
+                }
+                w!(self, "for ");
+                self.print_pat(*pat);
+                w!(self, " in ");
+                self.print_expr(*iterable);
+                self.print_expr(*body);
+            }
+            Expr::Call { callee, args, is_assignee_expr: _ } => {
+                self.print_expr(*callee);
+                w!(self, "(");
+                if !args.is_empty() {
+                    self.indented(|p| {
+                        for arg in &**args {
+                            p.print_expr(*arg);
+                            wln!(p, ",");
+                        }
+                    });
+                }
+                w!(self, ")");
+            }
+            Expr::MethodCall { receiver, method_name, args, generic_args } => {
+                self.print_expr(*receiver);
+                w!(self, ".{}", method_name);
+                if let Some(args) = generic_args {
+                    w!(self, "::<");
+                    print_generic_args(args, self).unwrap();
+                    w!(self, ">");
+                }
+                w!(self, "(");
+                if !args.is_empty() {
+                    self.indented(|p| {
+                        for arg in &**args {
+                            p.print_expr(*arg);
+                            wln!(p, ",");
+                        }
+                    });
+                }
+                w!(self, ")");
+            }
+            Expr::Match { expr, arms } => {
+                w!(self, "match ");
+                self.print_expr(*expr);
+                w!(self, " {{");
+                self.indented(|p| {
+                    for arm in &**arms {
+                        p.print_pat(arm.pat);
+                        if let Some(guard) = arm.guard {
+                            w!(p, " if ");
+                            p.print_expr(guard);
+                        }
+                        w!(p, " => ");
+                        p.print_expr(arm.expr);
+                        wln!(p, ",");
+                    }
+                });
+                wln!(self, "}}");
+            }
+            Expr::Continue { label } => {
+                w!(self, "continue");
+                if let Some(label) = label {
+                    w!(self, " {}", label);
+                }
+            }
+            Expr::Break { expr, label } => {
+                w!(self, "break");
+                if let Some(label) = label {
+                    w!(self, " {}", label);
+                }
+                if let Some(expr) = expr {
+                    self.whitespace();
+                    self.print_expr(*expr);
+                }
+            }
+            Expr::Return { expr } => {
+                w!(self, "return");
+                if let Some(expr) = expr {
+                    self.whitespace();
+                    self.print_expr(*expr);
+                }
+            }
+            Expr::Yield { expr } => {
+                w!(self, "yield");
+                if let Some(expr) = expr {
+                    self.whitespace();
+                    self.print_expr(*expr);
+                }
+            }
+            Expr::RecordLit { path, fields, spread, ellipsis, is_assignee_expr: _ } => {
+                match path {
+                    Some(path) => self.print_path(path),
+                    None => w!(self, "�"),
+                }
+
+                w!(self, "{{");
+                self.indented(|p| {
+                    for field in &**fields {
+                        w!(p, "{}: ", field.name);
+                        p.print_expr(field.expr);
+                        wln!(p, ",");
+                    }
+                    if let Some(spread) = spread {
+                        w!(p, "..");
+                        p.print_expr(*spread);
+                        wln!(p);
+                    }
+                    if *ellipsis {
+                        wln!(p, "..");
+                    }
+                });
+                w!(self, "}}");
+            }
+            Expr::Field { expr, name } => {
+                self.print_expr(*expr);
+                w!(self, ".{}", name);
+            }
+            Expr::Await { expr } => {
+                self.print_expr(*expr);
+                w!(self, ".await");
+            }
+            Expr::Try { expr } => {
+                self.print_expr(*expr);
+                w!(self, "?");
+            }
+            Expr::TryBlock { body } => {
+                w!(self, "try ");
+                self.print_expr(*body);
+            }
+            Expr::Async { body } => {
+                w!(self, "async ");
+                self.print_expr(*body);
+            }
+            Expr::Const { body } => {
+                w!(self, "const ");
+                self.print_expr(*body);
+            }
+            Expr::Cast { expr, type_ref } => {
+                self.print_expr(*expr);
+                w!(self, " as ");
+                self.print_type_ref(type_ref);
+            }
+            Expr::Ref { expr, rawness, mutability } => {
+                w!(self, "&");
+                if rawness.is_raw() {
+                    w!(self, "raw ");
+                }
+                if mutability.is_mut() {
+                    w!(self, "mut ");
+                }
+                self.print_expr(*expr);
+            }
+            Expr::Box { expr } => {
+                w!(self, "box ");
+                self.print_expr(*expr);
+            }
+            Expr::UnaryOp { expr, op } => {
+                let op = match op {
+                    ast::UnaryOp::Deref => "*",
+                    ast::UnaryOp::Not => "!",
+                    ast::UnaryOp::Neg => "-",
+                };
+                w!(self, "{}", op);
+                self.print_expr(*expr);
+            }
+            Expr::BinaryOp { lhs, rhs, op } => {
+                let (bra, ket) = match op {
+                    None | Some(ast::BinaryOp::Assignment { .. }) => ("", ""),
+                    _ => ("(", ")"),
+                };
+                w!(self, "{}", bra);
+                self.print_expr(*lhs);
+                w!(self, "{} ", ket);
+                match op {
+                    Some(op) => w!(self, "{}", op),
+                    None => w!(self, "�"), // :)
+                }
+                w!(self, " {}", bra);
+                self.print_expr(*rhs);
+                w!(self, "{}", ket);
+            }
+            Expr::Range { lhs, rhs, range_type } => {
+                if let Some(lhs) = lhs {
+                    w!(self, "(");
+                    self.print_expr(*lhs);
+                    w!(self, ") ");
+                }
+                let range = match range_type {
+                    ast::RangeOp::Exclusive => "..",
+                    ast::RangeOp::Inclusive => "..=",
+                };
+                w!(self, "{}", range);
+                if let Some(rhs) = rhs {
+                    w!(self, "(");
+                    self.print_expr(*rhs);
+                    w!(self, ") ");
+                }
+            }
+            Expr::Index { base, index } => {
+                self.print_expr(*base);
+                w!(self, "[");
+                self.print_expr(*index);
+                w!(self, "]");
+            }
+            Expr::Closure { args, arg_types, ret_type, body } => {
+                w!(self, "|");
+                for (i, (pat, ty)) in args.iter().zip(arg_types.iter()).enumerate() {
+                    if i != 0 {
+                        w!(self, ", ");
+                    }
+                    self.print_pat(*pat);
+                    if let Some(ty) = ty {
+                        w!(self, ": ");
+                        self.print_type_ref(ty);
+                    }
+                }
+                w!(self, "|");
+                if let Some(ret_ty) = ret_type {
+                    w!(self, " -> ");
+                    self.print_type_ref(ret_ty);
+                }
+                self.whitespace();
+                self.print_expr(*body);
+            }
+            Expr::Tuple { exprs, is_assignee_expr: _ } => {
+                w!(self, "(");
+                for expr in exprs.iter() {
+                    self.print_expr(*expr);
+                    w!(self, ", ");
+                }
+                w!(self, ")");
+            }
+            Expr::Unsafe { body } => {
+                w!(self, "unsafe ");
+                self.print_expr(*body);
+            }
+            Expr::Array(arr) => {
+                w!(self, "[");
+                if !matches!(arr, Array::ElementList { elements, .. } if elements.is_empty()) {
+                    self.indented(|p| match arr {
+                        Array::ElementList { elements, is_assignee_expr: _ } => {
+                            for elem in elements.iter() {
+                                p.print_expr(*elem);
+                                w!(p, ", ");
+                            }
+                        }
+                        Array::Repeat { initializer, repeat } => {
+                            p.print_expr(*initializer);
+                            w!(p, "; ");
+                            p.print_expr(*repeat);
+                        }
+                    });
+                    self.newline();
+                }
+                w!(self, "]");
+            }
+            Expr::Literal(lit) => self.print_literal(lit),
+            Expr::Block { id: _, statements, tail, label } => {
+                self.whitespace();
+                if let Some(lbl) = label {
+                    w!(self, "{}: ", self.body[*lbl].name);
+                }
+                w!(self, "{{");
+                if !statements.is_empty() || tail.is_some() {
+                    self.indented(|p| {
+                        for stmt in &**statements {
+                            p.print_stmt(stmt);
+                        }
+                        if let Some(tail) = tail {
+                            p.print_expr(*tail);
+                        }
+                        p.newline();
+                    });
+                }
+                w!(self, "}}");
+            }
+            Expr::MacroStmts { statements, tail } => {
+                w!(self, "{{ // macro statements");
+                self.indented(|p| {
+                    for stmt in statements.iter() {
+                        p.print_stmt(stmt);
+                    }
+                    if let Some(tail) = tail {
+                        p.print_expr(*tail);
+                    }
+                });
+                self.newline();
+                w!(self, "}}");
+            }
+        }
+    }
+
+    fn print_pat(&mut self, pat: PatId) {
+        let pat = &self.body[pat];
+
+        match pat {
+            Pat::Missing => w!(self, "�"),
+            Pat::Wild => w!(self, "_"),
+            Pat::Tuple { args, ellipsis } => {
+                w!(self, "(");
+                for (i, pat) in args.iter().enumerate() {
+                    if i != 0 {
+                        w!(self, ", ");
+                    }
+                    if *ellipsis == Some(i) {
+                        w!(self, ".., ");
+                    }
+                    self.print_pat(*pat);
+                }
+                w!(self, ")");
+            }
+            Pat::Or(pats) => {
+                for (i, pat) in pats.iter().enumerate() {
+                    if i != 0 {
+                        w!(self, " | ");
+                    }
+                    self.print_pat(*pat);
+                }
+            }
+            Pat::Record { path, args, ellipsis } => {
+                match path {
+                    Some(path) => self.print_path(path),
+                    None => w!(self, "�"),
+                }
+
+                w!(self, " {{");
+                self.indented(|p| {
+                    for arg in args.iter() {
+                        w!(p, "{}: ", arg.name);
+                        p.print_pat(arg.pat);
+                        wln!(p, ",");
+                    }
+                    if *ellipsis {
+                        wln!(p, "..");
+                    }
+                });
+                w!(self, "}}");
+            }
+            Pat::Range { start, end } => {
+                self.print_expr(*start);
+                w!(self, "...");
+                self.print_expr(*end);
+            }
+            Pat::Slice { prefix, slice, suffix } => {
+                w!(self, "[");
+                for pat in prefix.iter() {
+                    self.print_pat(*pat);
+                    w!(self, ", ");
+                }
+                if let Some(pat) = slice {
+                    self.print_pat(*pat);
+                    w!(self, ", ");
+                }
+                for pat in suffix.iter() {
+                    self.print_pat(*pat);
+                    w!(self, ", ");
+                }
+                w!(self, "]");
+            }
+            Pat::Path(path) => self.print_path(path),
+            Pat::Lit(expr) => self.print_expr(*expr),
+            Pat::Bind { mode, name, subpat } => {
+                let mode = match mode {
+                    BindingAnnotation::Unannotated => "",
+                    BindingAnnotation::Mutable => "mut ",
+                    BindingAnnotation::Ref => "ref ",
+                    BindingAnnotation::RefMut => "ref mut ",
+                };
+                w!(self, "{}{}", mode, name);
+                if let Some(pat) = subpat {
+                    self.whitespace();
+                    self.print_pat(*pat);
+                }
+            }
+            Pat::TupleStruct { path, args, ellipsis } => {
+                match path {
+                    Some(path) => self.print_path(path),
+                    None => w!(self, "�"),
+                }
+                w!(self, "(");
+                for (i, arg) in args.iter().enumerate() {
+                    if i != 0 {
+                        w!(self, ", ");
+                    }
+                    if *ellipsis == Some(i) {
+                        w!(self, ", ..");
+                    }
+                    self.print_pat(*arg);
+                }
+                w!(self, ")");
+            }
+            Pat::Ref { pat, mutability } => {
+                w!(self, "&");
+                if mutability.is_mut() {
+                    w!(self, "mut ");
+                }
+                self.print_pat(*pat);
+            }
+            Pat::Box { inner } => {
+                w!(self, "box ");
+                self.print_pat(*inner);
+            }
+            Pat::ConstBlock(c) => {
+                w!(self, "const ");
+                self.print_expr(*c);
+            }
+        }
+    }
+
+    fn print_stmt(&mut self, stmt: &Statement) {
+        match stmt {
+            Statement::Let { pat, type_ref, initializer, else_branch } => {
+                w!(self, "let ");
+                self.print_pat(*pat);
+                if let Some(ty) = type_ref {
+                    w!(self, ": ");
+                    self.print_type_ref(ty);
+                }
+                if let Some(init) = initializer {
+                    w!(self, " = ");
+                    self.print_expr(*init);
+                }
+                if let Some(els) = else_branch {
+                    w!(self, " else ");
+                    self.print_expr(*els);
+                }
+                wln!(self, ";");
+            }
+            Statement::Expr { expr, has_semi } => {
+                self.print_expr(*expr);
+                if *has_semi {
+                    w!(self, ";");
+                }
+                wln!(self);
+            }
+        }
+    }
+
+    fn print_literal(&mut self, literal: &Literal) {
+        match literal {
+            Literal::String(it) => w!(self, "{:?}", it),
+            Literal::ByteString(it) => w!(self, "\"{}\"", it.escape_ascii()),
+            Literal::Char(it) => w!(self, "'{}'", it.escape_debug()),
+            Literal::Bool(it) => w!(self, "{}", it),
+            Literal::Int(i, suffix) => {
+                w!(self, "{}", i);
+                if let Some(suffix) = suffix {
+                    w!(self, "{}", suffix);
+                }
+            }
+            Literal::Uint(i, suffix) => {
+                w!(self, "{}", i);
+                if let Some(suffix) = suffix {
+                    w!(self, "{}", suffix);
+                }
+            }
+            Literal::Float(f, suffix) => {
+                w!(self, "{}", f);
+                if let Some(suffix) = suffix {
+                    w!(self, "{}", suffix);
+                }
+            }
+        }
+    }
+
+    fn print_type_ref(&mut self, ty: &TypeRef) {
+        print_type_ref(ty, self).unwrap();
+    }
+
+    fn print_path(&mut self, path: &Path) {
+        print_path(path, self).unwrap();
+    }
+}
diff --git a/crates/hir-def/src/builtin_type.rs b/crates/hir-def/src/builtin_type.rs
index 25a408036f..dd69c3ab47 100644
--- a/crates/hir-def/src/builtin_type.rs
+++ b/crates/hir-def/src/builtin_type.rs
@@ -156,3 +156,38 @@ impl BuiltinFloat {
         Some(res)
     }
 }
+
+impl fmt::Display for BuiltinInt {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(match self {
+            BuiltinInt::Isize => "isize",
+            BuiltinInt::I8 => "i8",
+            BuiltinInt::I16 => "i16",
+            BuiltinInt::I32 => "i32",
+            BuiltinInt::I64 => "i64",
+            BuiltinInt::I128 => "i128",
+        })
+    }
+}
+
+impl fmt::Display for BuiltinUint {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(match self {
+            BuiltinUint::Usize => "usize",
+            BuiltinUint::U8 => "u8",
+            BuiltinUint::U16 => "u16",
+            BuiltinUint::U32 => "u32",
+            BuiltinUint::U64 => "u64",
+            BuiltinUint::U128 => "u128",
+        })
+    }
+}
+
+impl fmt::Display for BuiltinFloat {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(match self {
+            BuiltinFloat::F32 => "f32",
+            BuiltinFloat::F64 => "f64",
+        })
+    }
+}
diff --git a/crates/hir-def/src/expr.rs b/crates/hir-def/src/expr.rs
index c1b3788acb..4381b43c25 100644
--- a/crates/hir-def/src/expr.rs
+++ b/crates/hir-def/src/expr.rs
@@ -12,6 +12,8 @@
 //!
 //! See also a neighboring `body` module.
 
+use std::fmt;
+
 use hir_expand::name::Name;
 use la_arena::{Idx, RawIdx};
 
@@ -52,8 +54,8 @@ impl FloatTypeWrapper {
     }
 }
 
-impl std::fmt::Display for FloatTypeWrapper {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+impl fmt::Display for FloatTypeWrapper {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "{:?}", f64::from_bits(self.0))
     }
 }
diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs
index f12d9a1273..34dd817fd1 100644
--- a/crates/hir-def/src/item_tree/pretty.rs
+++ b/crates/hir-def/src/item_tree/pretty.rs
@@ -2,13 +2,10 @@
 
 use std::fmt::{self, Write};
 
-use itertools::Itertools;
-
 use crate::{
     attr::RawAttrs,
     generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget},
-    path::GenericArg,
-    type_ref::TraitBoundModifier,
+    pretty::{print_path, print_type_bounds, print_type_ref},
     visibility::RawVisibility,
 };
 
@@ -464,183 +461,15 @@ impl<'a> Printer<'a> {
     }
 
     fn print_type_ref(&mut self, type_ref: &TypeRef) {
-        // FIXME: deduplicate with `HirDisplay` impl
-        match type_ref {
-            TypeRef::Never => w!(self, "!"),
-            TypeRef::Placeholder => w!(self, "_"),
-            TypeRef::Tuple(fields) => {
-                w!(self, "(");
-                for (i, field) in fields.iter().enumerate() {
-                    if i != 0 {
-                        w!(self, ", ");
-                    }
-                    self.print_type_ref(field);
-                }
-                w!(self, ")");
-            }
-            TypeRef::Path(path) => self.print_path(path),
-            TypeRef::RawPtr(pointee, mtbl) => {
-                let mtbl = match mtbl {
-                    Mutability::Shared => "*const",
-                    Mutability::Mut => "*mut",
-                };
-                w!(self, "{} ", mtbl);
-                self.print_type_ref(pointee);
-            }
-            TypeRef::Reference(pointee, lt, mtbl) => {
-                let mtbl = match mtbl {
-                    Mutability::Shared => "",
-                    Mutability::Mut => "mut ",
-                };
-                w!(self, "&");
-                if let Some(lt) = lt {
-                    w!(self, "{} ", lt.name);
-                }
-                w!(self, "{}", mtbl);
-                self.print_type_ref(pointee);
-            }
-            TypeRef::Array(elem, len) => {
-                w!(self, "[");
-                self.print_type_ref(elem);
-                w!(self, "; {}]", len);
-            }
-            TypeRef::Slice(elem) => {
-                w!(self, "[");
-                self.print_type_ref(elem);
-                w!(self, "]");
-            }
-            TypeRef::Fn(args_and_ret, varargs) => {
-                let ((_, return_type), args) =
-                    args_and_ret.split_last().expect("TypeRef::Fn is missing return type");
-                w!(self, "fn(");
-                for (i, (_, typeref)) in args.iter().enumerate() {
-                    if i != 0 {
-                        w!(self, ", ");
-                    }
-                    self.print_type_ref(typeref);
-                }
-                if *varargs {
-                    if !args.is_empty() {
-                        w!(self, ", ");
-                    }
-                    w!(self, "...");
-                }
-                w!(self, ") -> ");
-                self.print_type_ref(return_type);
-            }
-            TypeRef::Macro(_ast_id) => {
-                w!(self, "<macro>");
-            }
-            TypeRef::Error => w!(self, "{{unknown}}"),
-            TypeRef::ImplTrait(bounds) => {
-                w!(self, "impl ");
-                self.print_type_bounds(bounds);
-            }
-            TypeRef::DynTrait(bounds) => {
-                w!(self, "dyn ");
-                self.print_type_bounds(bounds);
-            }
-        }
+        print_type_ref(type_ref, self).unwrap();
     }
 
     fn print_type_bounds(&mut self, bounds: &[Interned<TypeBound>]) {
-        for (i, bound) in bounds.iter().enumerate() {
-            if i != 0 {
-                w!(self, " + ");
-            }
-
-            match bound.as_ref() {
-                TypeBound::Path(path, modifier) => {
-                    match modifier {
-                        TraitBoundModifier::None => (),
-                        TraitBoundModifier::Maybe => w!(self, "?"),
-                    }
-                    self.print_path(path)
-                }
-                TypeBound::ForLifetime(lifetimes, path) => {
-                    w!(self, "for<{}> ", lifetimes.iter().format(", "));
-                    self.print_path(path);
-                }
-                TypeBound::Lifetime(lt) => w!(self, "{}", lt.name),
-                TypeBound::Error => w!(self, "{{unknown}}"),
-            }
-        }
+        print_type_bounds(bounds, self).unwrap();
     }
 
     fn print_path(&mut self, path: &Path) {
-        match path.type_anchor() {
-            Some(anchor) => {
-                w!(self, "<");
-                self.print_type_ref(anchor);
-                w!(self, ">::");
-            }
-            None => match path.kind() {
-                PathKind::Plain => {}
-                PathKind::Super(0) => w!(self, "self::"),
-                PathKind::Super(n) => {
-                    for _ in 0..*n {
-                        w!(self, "super::");
-                    }
-                }
-                PathKind::Crate => w!(self, "crate::"),
-                PathKind::Abs => w!(self, "::"),
-                PathKind::DollarCrate(_) => w!(self, "$crate::"),
-            },
-        }
-
-        for (i, segment) in path.segments().iter().enumerate() {
-            if i != 0 {
-                w!(self, "::");
-            }
-
-            w!(self, "{}", segment.name);
-            if let Some(generics) = segment.args_and_bindings {
-                // NB: these are all in type position, so `::<` turbofish syntax is not necessary
-                w!(self, "<");
-                let mut first = true;
-                let args = if generics.has_self_type {
-                    let (self_ty, args) = generics.args.split_first().unwrap();
-                    w!(self, "Self=");
-                    self.print_generic_arg(self_ty);
-                    first = false;
-                    args
-                } else {
-                    &generics.args
-                };
-                for arg in args {
-                    if !first {
-                        w!(self, ", ");
-                    }
-                    first = false;
-                    self.print_generic_arg(arg);
-                }
-                for binding in &generics.bindings {
-                    if !first {
-                        w!(self, ", ");
-                    }
-                    first = false;
-                    w!(self, "{}", binding.name);
-                    if !binding.bounds.is_empty() {
-                        w!(self, ": ");
-                        self.print_type_bounds(&binding.bounds);
-                    }
-                    if let Some(ty) = &binding.type_ref {
-                        w!(self, " = ");
-                        self.print_type_ref(ty);
-                    }
-                }
-
-                w!(self, ">");
-            }
-        }
-    }
-
-    fn print_generic_arg(&mut self, arg: &GenericArg) {
-        match arg {
-            GenericArg::Type(ty) => self.print_type_ref(ty),
-            GenericArg::Const(c) => w!(self, "{}", c),
-            GenericArg::Lifetime(lt) => w!(self, "{}", lt.name),
-        }
+        print_path(path, self).unwrap();
     }
 
     fn print_generic_params(&mut self, params: &GenericParams) {
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 56603f4b15..32ebfda4fd 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -53,6 +53,7 @@ pub mod import_map;
 mod test_db;
 #[cfg(test)]
 mod macro_expansion_tests;
+mod pretty;
 
 use std::{
     hash::{Hash, Hasher},
diff --git a/crates/hir-def/src/pretty.rs b/crates/hir-def/src/pretty.rs
new file mode 100644
index 0000000000..6636c8a23c
--- /dev/null
+++ b/crates/hir-def/src/pretty.rs
@@ -0,0 +1,209 @@
+//! Display and pretty printing routines.
+
+use std::fmt::{self, Write};
+
+use hir_expand::mod_path::PathKind;
+use itertools::Itertools;
+
+use crate::{
+    intern::Interned,
+    path::{GenericArg, GenericArgs, Path},
+    type_ref::{Mutability, TraitBoundModifier, TypeBound, TypeRef},
+};
+
+pub(crate) fn print_path(path: &Path, buf: &mut dyn Write) -> fmt::Result {
+    match path.type_anchor() {
+        Some(anchor) => {
+            write!(buf, "<")?;
+            print_type_ref(anchor, buf)?;
+            write!(buf, ">::")?;
+        }
+        None => match path.kind() {
+            PathKind::Plain => {}
+            PathKind::Super(0) => write!(buf, "self")?,
+            PathKind::Super(n) => {
+                for i in 0..*n {
+                    if i == 0 {
+                        buf.write_str("super")?;
+                    } else {
+                        buf.write_str("::super")?;
+                    }
+                }
+            }
+            PathKind::Crate => write!(buf, "crate")?,
+            PathKind::Abs => {}
+            PathKind::DollarCrate(_) => write!(buf, "$crate")?,
+        },
+    }
+
+    for (i, segment) in path.segments().iter().enumerate() {
+        if i != 0 || !matches!(path.kind(), PathKind::Plain) {
+            write!(buf, "::")?;
+        }
+
+        write!(buf, "{}", segment.name)?;
+        if let Some(generics) = segment.args_and_bindings {
+            write!(buf, "::<")?;
+            print_generic_args(generics, buf)?;
+
+            write!(buf, ">")?;
+        }
+    }
+
+    Ok(())
+}
+
+pub(crate) fn print_generic_args(generics: &GenericArgs, buf: &mut dyn Write) -> fmt::Result {
+    let mut first = true;
+    let args = if generics.has_self_type {
+        let (self_ty, args) = generics.args.split_first().unwrap();
+        write!(buf, "Self=")?;
+        print_generic_arg(self_ty, buf)?;
+        first = false;
+        args
+    } else {
+        &generics.args
+    };
+    for arg in args {
+        if !first {
+            write!(buf, ", ")?;
+        }
+        first = false;
+        print_generic_arg(arg, buf)?;
+    }
+    for binding in &generics.bindings {
+        if !first {
+            write!(buf, ", ")?;
+        }
+        first = false;
+        write!(buf, "{}", binding.name)?;
+        if !binding.bounds.is_empty() {
+            write!(buf, ": ")?;
+            print_type_bounds(&binding.bounds, buf)?;
+        }
+        if let Some(ty) = &binding.type_ref {
+            write!(buf, " = ")?;
+            print_type_ref(ty, buf)?;
+        }
+    }
+    Ok(())
+}
+
+pub(crate) fn print_generic_arg(arg: &GenericArg, buf: &mut dyn Write) -> fmt::Result {
+    match arg {
+        GenericArg::Type(ty) => print_type_ref(ty, buf),
+        GenericArg::Const(c) => write!(buf, "{}", c),
+        GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name),
+    }
+}
+
+pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Result {
+    // FIXME: deduplicate with `HirDisplay` impl
+    match type_ref {
+        TypeRef::Never => write!(buf, "!")?,
+        TypeRef::Placeholder => write!(buf, "_")?,
+        TypeRef::Tuple(fields) => {
+            write!(buf, "(")?;
+            for (i, field) in fields.iter().enumerate() {
+                if i != 0 {
+                    write!(buf, ", ")?;
+                }
+                print_type_ref(field, buf)?;
+            }
+            write!(buf, ")")?;
+        }
+        TypeRef::Path(path) => print_path(path, buf)?,
+        TypeRef::RawPtr(pointee, mtbl) => {
+            let mtbl = match mtbl {
+                Mutability::Shared => "*const",
+                Mutability::Mut => "*mut",
+            };
+            write!(buf, "{} ", mtbl)?;
+            print_type_ref(pointee, buf)?;
+        }
+        TypeRef::Reference(pointee, lt, mtbl) => {
+            let mtbl = match mtbl {
+                Mutability::Shared => "",
+                Mutability::Mut => "mut ",
+            };
+            write!(buf, "&")?;
+            if let Some(lt) = lt {
+                write!(buf, "{} ", lt.name)?;
+            }
+            write!(buf, "{}", mtbl)?;
+            print_type_ref(pointee, buf)?;
+        }
+        TypeRef::Array(elem, len) => {
+            write!(buf, "[")?;
+            print_type_ref(elem, buf)?;
+            write!(buf, "; {}]", len)?;
+        }
+        TypeRef::Slice(elem) => {
+            write!(buf, "[")?;
+            print_type_ref(elem, buf)?;
+            write!(buf, "]")?;
+        }
+        TypeRef::Fn(args_and_ret, varargs) => {
+            let ((_, return_type), args) =
+                args_and_ret.split_last().expect("TypeRef::Fn is missing return type");
+            write!(buf, "fn(")?;
+            for (i, (_, typeref)) in args.iter().enumerate() {
+                if i != 0 {
+                    write!(buf, ", ")?;
+                }
+                print_type_ref(typeref, buf)?;
+            }
+            if *varargs {
+                if !args.is_empty() {
+                    write!(buf, ", ")?;
+                }
+                write!(buf, "...")?;
+            }
+            write!(buf, ") -> ")?;
+            print_type_ref(return_type, buf)?;
+        }
+        TypeRef::Macro(_ast_id) => {
+            write!(buf, "<macro>")?;
+        }
+        TypeRef::Error => write!(buf, "{{unknown}}")?,
+        TypeRef::ImplTrait(bounds) => {
+            write!(buf, "impl ")?;
+            print_type_bounds(bounds, buf)?;
+        }
+        TypeRef::DynTrait(bounds) => {
+            write!(buf, "dyn ")?;
+            print_type_bounds(bounds, buf)?;
+        }
+    }
+
+    Ok(())
+}
+
+pub(crate) fn print_type_bounds(
+    bounds: &[Interned<TypeBound>],
+    buf: &mut dyn Write,
+) -> fmt::Result {
+    for (i, bound) in bounds.iter().enumerate() {
+        if i != 0 {
+            write!(buf, " + ")?;
+        }
+
+        match bound.as_ref() {
+            TypeBound::Path(path, modifier) => {
+                match modifier {
+                    TraitBoundModifier::None => (),
+                    TraitBoundModifier::Maybe => write!(buf, "?")?,
+                }
+                print_path(path, buf)?;
+            }
+            TypeBound::ForLifetime(lifetimes, path) => {
+                write!(buf, "for<{}> ", lifetimes.iter().format(", "))?;
+                print_path(path, buf)?;
+            }
+            TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name)?,
+            TypeBound::Error => write!(buf, "{{unknown}}")?,
+        }
+    }
+
+    Ok(())
+}
diff --git a/crates/hir-def/src/type_ref.rs b/crates/hir-def/src/type_ref.rs
index 9248059627..5b4c71be7f 100644
--- a/crates/hir-def/src/type_ref.rs
+++ b/crates/hir-def/src/type_ref.rs
@@ -77,6 +77,10 @@ impl Rawness {
             Rawness::Ref
         }
     }
+
+    pub fn is_raw(&self) -> bool {
+        matches!(self, Self::RawPtr)
+    }
 }
 
 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 8f984210e1..3561bdeba7 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -72,7 +72,7 @@ use itertools::Itertools;
 use nameres::diagnostics::DefDiagnosticKind;
 use once_cell::unsync::Lazy;
 use rustc_hash::FxHashSet;
-use stdx::{format_to, impl_from, never};
+use stdx::{impl_from, never};
 use syntax::{
     ast::{self, HasAttrs as _, HasDocComments, HasName},
     AstNode, AstPtr, SmolStr, SyntaxNodePtr, TextRange, T,
@@ -1136,6 +1136,20 @@ impl DefWithBody {
         }
     }
 
+    fn id(&self) -> DefWithBodyId {
+        match self {
+            DefWithBody::Function(it) => it.id.into(),
+            DefWithBody::Static(it) => it.id.into(),
+            DefWithBody::Const(it) => it.id.into(),
+        }
+    }
+
+    /// A textual representation of the HIR of this def's body for debugging purposes.
+    pub fn debug_hir(self, db: &dyn HirDatabase) -> String {
+        let body = db.body(self.id());
+        body.pretty_print(db.upcast(), self.id())
+    }
+
     pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) {
         let krate = self.module(db).id.krate();
 
@@ -1470,19 +1484,6 @@ impl Function {
         let def_map = db.crate_def_map(loc.krate(db).into());
         def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() })
     }
-
-    /// A textual representation of the HIR of this function for debugging purposes.
-    pub fn debug_hir(self, db: &dyn HirDatabase) -> String {
-        let body = db.body(self.id.into());
-
-        let mut result = String::new();
-        format_to!(result, "HIR expressions in the body of `{}`:\n", self.name(db));
-        for (id, expr) in body.exprs.iter() {
-            format_to!(result, "{:?}: {:?}\n", id, expr);
-        }
-
-        result
-    }
 }
 
 // Note: logically, this belongs to `hir_ty`, but we are not using it there yet.
diff --git a/crates/ide/src/view_hir.rs b/crates/ide/src/view_hir.rs
index bf0835ed7e..d2bbbf6d26 100644
--- a/crates/ide/src/view_hir.rs
+++ b/crates/ide/src/view_hir.rs
@@ -1,4 +1,4 @@
-use hir::{Function, Semantics};
+use hir::{DefWithBody, Semantics};
 use ide_db::base_db::FilePosition;
 use ide_db::RootDatabase;
 use syntax::{algo::find_node_at_offset, ast, AstNode};
@@ -19,8 +19,12 @@ fn body_hir(db: &RootDatabase, position: FilePosition) -> Option<String> {
     let sema = Semantics::new(db);
     let source_file = sema.parse(position.file_id);
 
-    let function = find_node_at_offset::<ast::Fn>(source_file.syntax(), position.offset)?;
-
-    let function: Function = sema.to_def(&function)?;
-    Some(function.debug_hir(db))
+    let item = find_node_at_offset::<ast::Item>(source_file.syntax(), position.offset)?;
+    let def: DefWithBody = match item {
+        ast::Item::Fn(it) => sema.to_def(&it)?.into(),
+        ast::Item::Const(it) => sema.to_def(&it)?.into(),
+        ast::Item::Static(it) => sema.to_def(&it)?.into(),
+        _ => return None,
+    };
+    Some(def.debug_hir(db))
 }
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 1b793bb0b1..f58de9da1b 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -433,7 +433,7 @@ export function syntaxTree(ctx: Ctx): Cmd {
 // The contents of the file come from the `TextDocumentContentProvider`
 export function viewHir(ctx: Ctx): Cmd {
     const tdcp = new (class implements vscode.TextDocumentContentProvider {
-        readonly uri = vscode.Uri.parse("rust-analyzer-hir://viewHir/hir.txt");
+        readonly uri = vscode.Uri.parse("rust-analyzer-hir://viewHir/hir.rs");
         readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
         constructor() {
             vscode.workspace.onDidChangeTextDocument(


Test patch:
diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs
index 5cdf36cc61..e30d9652bb 100644
--- a/crates/hir-def/src/item_tree/tests.rs
+++ b/crates/hir-def/src/item_tree/tests.rs
@@ -283,10 +283,10 @@ struct S {
         "#,
         expect![[r#"
             pub(self) struct S {
-                pub(self) a: Mixed<'a, T, Item = (), OtherItem = u8>,
-                pub(self) b: Qualified<Self=Fully>::Syntax,
-                pub(self) c: <TypeAnchored>::Path<'a>,
-                pub(self) d: dyn for<'a> Trait<'a>,
+                pub(self) a: Mixed::<'a, T, Item = (), OtherItem = u8>,
+                pub(self) b: Qualified::<Self=Fully>::Syntax,
+                pub(self) c: <TypeAnchored>::Path::<'a>,
+                pub(self) d: dyn for<'a> Trait::<'a>,
             }
         "#]],
     )
@@ -329,7 +329,7 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
                 T: Copy,
                 U: ?Sized;
 
-            impl<'a, 'b, T, const K: u8> S<'a, 'b, T, K>
+            impl<'a, 'b, T, const K: u8> S::<'a, 'b, T, K>
             where
                 T: Copy,
                 T: 'a,
@@ -352,7 +352,7 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {}
             where
                 Self: Super,
                 T: 'a,
-                Self: for<'a> Tr<'a, T>
+                Self: for<'a> Tr::<'a, T>
             {
             }
         "#]],