Skip to content

Commit

Permalink
Auto merge of rust-lang#18075 - roife:fix-issue-17858, r=Veykril
Browse files Browse the repository at this point in the history
feat: render patterns in params for hovering

Fix rust-lang#17858

This PR introduces an option to [hir-def/src/body/pretty.rs](https://github.com/rust-lang/rust-analyzer/blob/08c7bbc2dbe4dcc8968484f1a0e1e6fe7a1d4f6d/crates/hir-def/src/body/pretty.rs) to render the result as a single line, which is then reused for rendering patterns in parameters for hovering.
  • Loading branch information
bors committed Sep 11, 2024
2 parents 4ed9c8d + 129acd2 commit 51cc5a7
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 24 deletions.
11 changes: 11 additions & 0 deletions src/tools/rust-analyzer/crates/hir-def/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,17 @@ impl Body {
pretty::print_expr_hir(db, self, owner, expr, edition)
}

pub fn pretty_print_pat(
&self,
db: &dyn DefDatabase,
owner: DefWithBodyId,
pat: PatId,
oneline: bool,
edition: Edition,
) -> String {
pretty::print_pat_hir(db, self, owner, pat, oneline, edition)
}

fn new(
db: &dyn DefDatabase,
owner: DefWithBodyId,
Expand Down
125 changes: 106 additions & 19 deletions src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ use crate::{

use super::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum LineFormat {
Oneline,
Newline,
Indentation,
}

pub(super) fn print_body_hir(
db: &dyn DefDatabase,
body: &Body,
Expand Down Expand Up @@ -52,7 +59,14 @@ pub(super) fn print_body_hir(
}
};

let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false, edition };
let mut p = Printer {
db,
body,
buf: header,
indent_level: 0,
line_format: LineFormat::Newline,
edition,
};
if let DefWithBodyId::FunctionId(it) = owner {
p.buf.push('(');
let function_data = &db.function_data(it);
Expand Down Expand Up @@ -95,12 +109,38 @@ pub(super) fn print_expr_hir(
expr: ExprId,
edition: Edition,
) -> String {
let mut p =
Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false, edition };
let mut p = Printer {
db,
body,
buf: String::new(),
indent_level: 0,
line_format: LineFormat::Newline,
edition,
};
p.print_expr(expr);
p.buf
}

pub(super) fn print_pat_hir(
db: &dyn DefDatabase,
body: &Body,
_owner: DefWithBodyId,
pat: PatId,
oneline: bool,
edition: Edition,
) -> String {
let mut p = Printer {
db,
body,
buf: String::new(),
indent_level: 0,
line_format: if oneline { LineFormat::Oneline } else { LineFormat::Newline },
edition,
};
p.print_pat(pat);
p.buf
}

macro_rules! w {
($dst:expr, $($arg:tt)*) => {
{ let _ = write!($dst, $($arg)*); }
Expand All @@ -109,10 +149,10 @@ macro_rules! w {

macro_rules! wln {
($dst:expr) => {
{ let _ = writeln!($dst); }
{ $dst.newline(); }
};
($dst:expr, $($arg:tt)*) => {
{ let _ = writeln!($dst, $($arg)*); }
{ let _ = w!($dst, $($arg)*); $dst.newline(); }
};
}

Expand All @@ -121,24 +161,30 @@ struct Printer<'a> {
body: &'a Body,
buf: String,
indent_level: usize,
needs_indent: bool,
line_format: LineFormat,
edition: Edition,
}

impl Write for Printer<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for line in s.split_inclusive('\n') {
if self.needs_indent {
if matches!(self.line_format, LineFormat::Indentation) {
match self.buf.chars().rev().find(|ch| *ch != ' ') {
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');

if matches!(self.line_format, LineFormat::Newline | LineFormat::Indentation) {
self.line_format = if line.ends_with('\n') {
LineFormat::Indentation
} else {
LineFormat::Newline
};
}
}

Ok(())
Expand All @@ -161,14 +207,28 @@ impl Printer<'_> {
}
}

// Add a newline if the current line is not empty.
// If the current line is empty, add a space instead.
//
// Do not use [`writeln!()`] or [`wln!()`] here, which will result in
// infinite recursive calls to this function.
fn newline(&mut self) {
match self.buf.chars().rev().find_position(|ch| *ch != ' ') {
Some((_, '\n')) | None => {}
Some((idx, _)) => {
if idx != 0 {
self.buf.drain(self.buf.len() - idx..);
if matches!(self.line_format, LineFormat::Oneline) {
match self.buf.chars().last() {
Some(' ') | None => {}
Some(_) => {
w!(self, " ");
}
}
} else {
match self.buf.chars().rev().find_position(|ch| *ch != ' ') {
Some((_, '\n')) | None => {}
Some((idx, _)) => {
if idx != 0 {
self.buf.drain(self.buf.len() - idx..);
}
w!(self, "\n");
}
writeln!(self).unwrap()
}
}
}
Expand Down Expand Up @@ -539,12 +599,14 @@ impl Printer<'_> {
w!(self, ")");
}
Pat::Or(pats) => {
w!(self, "(");
for (i, pat) in pats.iter().enumerate() {
if i != 0 {
w!(self, " | ");
}
self.print_pat(*pat);
}
w!(self, ")");
}
Pat::Record { path, args, ellipsis } => {
match path {
Expand All @@ -554,12 +616,37 @@ impl Printer<'_> {

w!(self, " {{");
let edition = self.edition;
let oneline = matches!(self.line_format, LineFormat::Oneline);
self.indented(|p| {
for arg in args.iter() {
w!(p, "{}: ", arg.name.display(self.db.upcast(), edition));
p.print_pat(arg.pat);
wln!(p, ",");
for (idx, arg) in args.iter().enumerate() {
let field_name = arg.name.display(self.db.upcast(), edition).to_string();

let mut same_name = false;
if let Pat::Bind { id, subpat: None } = &self.body[arg.pat] {
if let Binding { name, mode: BindingAnnotation::Unannotated, .. } =
&self.body.bindings[*id]
{
if name.as_str() == field_name {
same_name = true;
}
}
}

w!(p, "{}", field_name);

if !same_name {
w!(p, ": ");
p.print_pat(arg.pat);
}

// Do not print the extra comma if the line format is oneline
if oneline && idx == args.len() - 1 {
w!(p, " ");
} else {
wln!(p, ",");
}
}

if *ellipsis {
wln!(p, "..");
}
Expand Down
13 changes: 8 additions & 5 deletions src/tools/rust-analyzer/crates/hir/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,20 @@ impl HirDisplay for Function {
}

// FIXME: Use resolved `param.ty` once we no longer discard lifetimes
let body = db.body(self.id.into());
for (type_ref, param) in data.params.iter().zip(self.assoc_fn_params(db)).skip(skip_self) {
let local = param.as_local(db).map(|it| it.name(db));
if !first {
f.write_str(", ")?;
} else {
first = false;
}
match local {
Some(name) => write!(f, "{}: ", name.display(f.db.upcast(), f.edition()))?,
None => f.write_str("_: ")?,
}

let pat_id = body.params[param.idx - body.self_param.is_some() as usize];
let pat_str =
body.pretty_print_pat(db.upcast(), self.id.into(), pat_id, true, f.edition());
f.write_str(&pat_str)?;

f.write_str(": ")?;
type_ref.hir_fmt(f)?;
}

Expand Down
Loading

0 comments on commit 51cc5a7

Please sign in to comment.