mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 12:29:21 +00:00
Auto merge of #18075 - roife:fix-issue-17858, r=Veykril
feat: render patterns in params for hovering
Fix #17858
This PR introduces an option to [hir-def/src/body/pretty.rs](08c7bbc2db/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.
This commit is contained in:
commit
c54a827f50
4 changed files with 303 additions and 24 deletions
|
@ -227,6 +227,17 @@ impl Body {
|
||||||
pretty::print_expr_hir(db, self, owner, expr, edition)
|
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(
|
fn new(
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
owner: DefWithBodyId,
|
owner: DefWithBodyId,
|
||||||
|
|
|
@ -16,6 +16,13 @@ use crate::{
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(super) enum LineFormat {
|
||||||
|
Oneline,
|
||||||
|
Newline,
|
||||||
|
Indentation,
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn print_body_hir(
|
pub(super) fn print_body_hir(
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
body: &Body,
|
body: &Body,
|
||||||
|
@ -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 {
|
if let DefWithBodyId::FunctionId(it) = owner {
|
||||||
p.buf.push('(');
|
p.buf.push('(');
|
||||||
let function_data = &db.function_data(it);
|
let function_data = &db.function_data(it);
|
||||||
|
@ -95,12 +109,38 @@ pub(super) fn print_expr_hir(
|
||||||
expr: ExprId,
|
expr: ExprId,
|
||||||
edition: Edition,
|
edition: Edition,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut p =
|
let mut p = Printer {
|
||||||
Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false, edition };
|
db,
|
||||||
|
body,
|
||||||
|
buf: String::new(),
|
||||||
|
indent_level: 0,
|
||||||
|
line_format: LineFormat::Newline,
|
||||||
|
edition,
|
||||||
|
};
|
||||||
p.print_expr(expr);
|
p.print_expr(expr);
|
||||||
p.buf
|
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 {
|
macro_rules! w {
|
||||||
($dst:expr, $($arg:tt)*) => {
|
($dst:expr, $($arg:tt)*) => {
|
||||||
{ let _ = write!($dst, $($arg)*); }
|
{ let _ = write!($dst, $($arg)*); }
|
||||||
|
@ -109,10 +149,10 @@ macro_rules! w {
|
||||||
|
|
||||||
macro_rules! wln {
|
macro_rules! wln {
|
||||||
($dst:expr) => {
|
($dst:expr) => {
|
||||||
{ let _ = writeln!($dst); }
|
{ $dst.newline(); }
|
||||||
};
|
};
|
||||||
($dst:expr, $($arg:tt)*) => {
|
($dst:expr, $($arg:tt)*) => {
|
||||||
{ let _ = writeln!($dst, $($arg)*); }
|
{ let _ = w!($dst, $($arg)*); $dst.newline(); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,24 +161,30 @@ struct Printer<'a> {
|
||||||
body: &'a Body,
|
body: &'a Body,
|
||||||
buf: String,
|
buf: String,
|
||||||
indent_level: usize,
|
indent_level: usize,
|
||||||
needs_indent: bool,
|
line_format: LineFormat,
|
||||||
edition: Edition,
|
edition: Edition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for Printer<'_> {
|
impl Write for Printer<'_> {
|
||||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
for line in s.split_inclusive('\n') {
|
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 != ' ') {
|
match self.buf.chars().rev().find(|ch| *ch != ' ') {
|
||||||
Some('\n') | None => {}
|
Some('\n') | None => {}
|
||||||
_ => self.buf.push('\n'),
|
_ => self.buf.push('\n'),
|
||||||
}
|
}
|
||||||
self.buf.push_str(&" ".repeat(self.indent_level));
|
self.buf.push_str(&" ".repeat(self.indent_level));
|
||||||
self.needs_indent = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.buf.push_str(line);
|
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(())
|
Ok(())
|
||||||
|
@ -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) {
|
fn newline(&mut self) {
|
||||||
match self.buf.chars().rev().find_position(|ch| *ch != ' ') {
|
if matches!(self.line_format, LineFormat::Oneline) {
|
||||||
Some((_, '\n')) | None => {}
|
match self.buf.chars().last() {
|
||||||
Some((idx, _)) => {
|
Some(' ') | None => {}
|
||||||
if idx != 0 {
|
Some(_) => {
|
||||||
self.buf.drain(self.buf.len() - idx..);
|
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -539,12 +599,14 @@ impl Printer<'_> {
|
||||||
w!(self, ")");
|
w!(self, ")");
|
||||||
}
|
}
|
||||||
Pat::Or(pats) => {
|
Pat::Or(pats) => {
|
||||||
|
w!(self, "(");
|
||||||
for (i, pat) in pats.iter().enumerate() {
|
for (i, pat) in pats.iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
w!(self, " | ");
|
w!(self, " | ");
|
||||||
}
|
}
|
||||||
self.print_pat(*pat);
|
self.print_pat(*pat);
|
||||||
}
|
}
|
||||||
|
w!(self, ")");
|
||||||
}
|
}
|
||||||
Pat::Record { path, args, ellipsis } => {
|
Pat::Record { path, args, ellipsis } => {
|
||||||
match path {
|
match path {
|
||||||
|
@ -554,12 +616,37 @@ impl Printer<'_> {
|
||||||
|
|
||||||
w!(self, " {{");
|
w!(self, " {{");
|
||||||
let edition = self.edition;
|
let edition = self.edition;
|
||||||
|
let oneline = matches!(self.line_format, LineFormat::Oneline);
|
||||||
self.indented(|p| {
|
self.indented(|p| {
|
||||||
for arg in args.iter() {
|
for (idx, arg) in args.iter().enumerate() {
|
||||||
w!(p, "{}: ", arg.name.display(self.db.upcast(), edition));
|
let field_name = arg.name.display(self.db.upcast(), edition).to_string();
|
||||||
p.print_pat(arg.pat);
|
|
||||||
wln!(p, ",");
|
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 {
|
if *ellipsis {
|
||||||
wln!(p, "..");
|
wln!(p, "..");
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,17 +99,20 @@ impl HirDisplay for Function {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Use resolved `param.ty` once we no longer discard lifetimes
|
// 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) {
|
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 {
|
if !first {
|
||||||
f.write_str(", ")?;
|
f.write_str(", ")?;
|
||||||
} else {
|
} else {
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
match local {
|
|
||||||
Some(name) => write!(f, "{}: ", name.display(f.db.upcast(), f.edition()))?,
|
let pat_id = body.params[param.idx - body.self_param.is_some() as usize];
|
||||||
None => f.write_str("_: ")?,
|
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)?;
|
type_ref.hir_fmt(f)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8742,3 +8742,181 @@ fn foo() {
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hover_function_with_pat_param() {
|
||||||
|
check(
|
||||||
|
r#"fn test_1$0((start_range, end_range): (u32, u32), a: i32) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_1*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_1((start_range, end_range): (u32, u32), a: i32)
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with tuple pattern and mutable parameters
|
||||||
|
check(
|
||||||
|
r#"fn test_2$0((mut x, y): (i32, i32)) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_2*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_2((mut x, y): (i32, i32))
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with a pattern in a reference type
|
||||||
|
check(
|
||||||
|
r#"fn test_3$0(&(a, b): &(i32, i32)) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_3*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_3(&(a, b): &(i32, i32))
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with complex pattern (struct destructuring)
|
||||||
|
check(
|
||||||
|
r#"struct Point { x: i32, y: i32 } fn test_4$0(Point { x, y }: Point) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_4*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_4(Point { x, y }: Point)
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with a nested pattern
|
||||||
|
check(
|
||||||
|
r#"fn test_5$0(((a, b), c): ((i32, i32), i32)) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_5*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_5(((a, b), c): ((i32, i32), i32))
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with an unused variable in the pattern
|
||||||
|
check(
|
||||||
|
r#"fn test_6$0((_, y): (i32, i64)) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_6*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_6((_, y): (i32, i64))
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with a complex pattern involving both tuple and struct
|
||||||
|
check(
|
||||||
|
r#"struct Foo { a: i32, b: i32 } fn test_7$0((x, Foo { a, b }): (i32, Foo)) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_7*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_7((x, Foo { a, b }): (i32, Foo))
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with Enum and Or pattern
|
||||||
|
check(
|
||||||
|
r#"enum MyEnum { A(i32), B(i32) } fn test_8$0((MyEnum::A(x) | MyEnum::B(x)): MyEnum) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_8*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_8((MyEnum::A(x) | MyEnum::B(x)): MyEnum)
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with a pattern as a function parameter
|
||||||
|
check(
|
||||||
|
r#"struct Foo { a: i32, b: i32 } fn test_9$0(Foo { a, b }: Foo) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_9*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_9(Foo { a, b }: Foo)
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with a pattern as a function parameter with a different name
|
||||||
|
check(
|
||||||
|
r#"struct Foo { a: i32, b: i32 } fn test_10$0(Foo { a, b: b1 }: Foo) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_10*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_10(Foo { a, b: b1 }: Foo)
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test case with a pattern as a function parameter with annotations
|
||||||
|
check(
|
||||||
|
r#"struct Foo { a: i32, b: i32 } fn test_10$0(Foo { a, b: mut b }: Foo) {}"#,
|
||||||
|
expect![[r#"
|
||||||
|
*test_10*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn test_10(Foo { a, b: mut b }: Foo)
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue