Skip to content

Commit 5664a2b

Browse files
bors[bot]lrlnayoshuawuyts
authored
Merge #9814
9814: Generate default impl when converting `#[derive(Debug)]` to manual impl r=yoshuawuyts a=yoshuawuyts This patch makes it so when you convert `#[derive(Debug)]` to a manual impl, a default body is provided that's equivalent to the original output of `#[derive(Debug)]`. This should make it drastically easier to write custom `Debug` impls, especially when all you want to do is quickly omit a single field which is `!Debug`. This is implemented for enums, record structs, tuple structs, empty structs - and it sets us up to implement variations on this in the future for other traits (like `PartialEq` and `Hash`). Thanks! ## Codegen diff This is the difference in codegen for record structs with this patch: ```diff struct Foo { bar: String, } impl fmt::Debug for Foo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - todo!(); + f.debug_struct("Foo").field("bar", &self.bar).finish() } } ``` Co-authored-by: Irina Shestak <[email protected]> Co-authored-by: Yoshua Wuyts <[email protected]> Co-authored-by: Yoshua Wuyts <[email protected]>
2 parents 044d99e + 59cdb51 commit 5664a2b

File tree

6 files changed

+209
-28
lines changed

6 files changed

+209
-28
lines changed

crates/ide/src/hover.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2732,8 +2732,8 @@ fn foo() {
27322732
file_id: FileId(
27332733
1,
27342734
),
2735-
full_range: 251..433,
2736-
focus_range: 290..296,
2735+
full_range: 252..434,
2736+
focus_range: 291..297,
27372737
name: "Future",
27382738
kind: Trait,
27392739
description: "pub trait Future",

crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs

Lines changed: 185 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use hir::ModuleDef;
22
use ide_db::helpers::{import_assets::NameToImport, mod_path_to_ast};
33
use ide_db::items_locator;
44
use itertools::Itertools;
5+
use syntax::ast::edit::AstNodeEdit;
6+
use syntax::ted;
57
use syntax::{
68
ast::{self, make, AstNode, NameOwner},
79
SyntaxKind::{IDENT, WHITESPACE},
@@ -32,8 +34,8 @@ use crate::{
3234
// struct S;
3335
//
3436
// impl Debug for S {
35-
// fn fmt(&self, f: &mut Formatter) -> Result<()> {
36-
// ${0:todo!()}
37+
// $0fn fmt(&self, f: &mut Formatter) -> Result<()> {
38+
// f.debug_struct("S").finish()
3739
// }
3840
// }
3941
// ```
@@ -111,7 +113,7 @@ fn add_assist(
111113
|builder| {
112114
let insert_pos = adt.syntax().text_range().end();
113115
let impl_def_with_items =
114-
impl_def_from_trait(&ctx.sema, &annotated_name, trait_, trait_path);
116+
impl_def_from_trait(&ctx.sema, adt, &annotated_name, trait_, trait_path);
115117
update_attribute(builder, input, &trait_name, attr);
116118
let trait_path = format!("{}", trait_path);
117119
match (ctx.config.snippet_cap, impl_def_with_items) {
@@ -149,6 +151,7 @@ fn add_assist(
149151

150152
fn impl_def_from_trait(
151153
sema: &hir::Semantics<ide_db::RootDatabase>,
154+
adt: &ast::Adt,
152155
annotated_name: &ast::Name,
153156
trait_: Option<hir::Trait>,
154157
trait_path: &ast::Path,
@@ -163,9 +166,116 @@ fn impl_def_from_trait(
163166
make::impl_trait(trait_path.clone(), make::ext::ident_path(&annotated_name.text()));
164167
let (impl_def, first_assoc_item) =
165168
add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
169+
170+
// Generate a default `impl` function body for the derived trait.
171+
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
172+
let _ = gen_default_impl(func, trait_path, adt, annotated_name);
173+
};
174+
166175
Some((impl_def, first_assoc_item))
167176
}
168177

178+
/// Generate custom trait bodies where possible.
179+
///
180+
/// Returns `Option` so that we can use `?` rather than `if let Some`. Returning
181+
/// `None` means that generating a custom trait body failed, and the body will remain
182+
/// as `todo!` instead.
183+
fn gen_default_impl(
184+
func: &ast::Fn,
185+
trait_path: &ast::Path,
186+
adt: &ast::Adt,
187+
annotated_name: &ast::Name,
188+
) -> Option<()> {
189+
match trait_path.segment()?.name_ref()?.text().as_str() {
190+
"Debug" => gen_debug_impl(adt, func, annotated_name),
191+
_ => Some(()),
192+
}
193+
}
194+
195+
/// Generate a `Debug` impl based on the fields and members of the target type.
196+
fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn, annotated_name: &ast::Name) -> Option<()> {
197+
match adt {
198+
// `Debug` cannot be derived for unions, so no default impl can be provided.
199+
ast::Adt::Union(_) => Some(()),
200+
201+
// => match self { Self::Variant => write!(f, "Variant") }
202+
ast::Adt::Enum(enum_) => {
203+
let list = enum_.variant_list()?;
204+
let mut arms = vec![];
205+
for variant in list.variants() {
206+
let name = variant.name()?;
207+
let left = make::ext::ident_path("Self");
208+
let right = make::ext::ident_path(&format!("{}", name));
209+
let variant_name = make::path_pat(make::path_concat(left, right));
210+
211+
let target = make::expr_path(make::ext::ident_path("f").into());
212+
let fmt_string = make::expr_literal(&(format!("\"{}\"", name))).into();
213+
let args = make::arg_list(vec![target, fmt_string]);
214+
let macro_name = make::expr_path(make::ext::ident_path("write"));
215+
let macro_call = make::expr_macro_call(macro_name, args);
216+
217+
arms.push(make::match_arm(Some(variant_name.into()), None, macro_call.into()));
218+
}
219+
220+
let match_target = make::expr_path(make::ext::ident_path("self"));
221+
let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1));
222+
let match_expr = make::expr_match(match_target, list);
223+
224+
let body = make::block_expr(None, Some(match_expr));
225+
let body = body.indent(ast::edit::IndentLevel(1));
226+
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
227+
Some(())
228+
}
229+
230+
ast::Adt::Struct(strukt) => {
231+
let name = format!("\"{}\"", annotated_name);
232+
let args = make::arg_list(Some(make::expr_literal(&name).into()));
233+
let target = make::expr_path(make::ext::ident_path("f"));
234+
235+
let expr = match strukt.field_list() {
236+
// => f.debug_struct("Name").finish()
237+
None => make::expr_method_call(target, make::name_ref("debug_struct"), args),
238+
239+
// => f.debug_struct("Name").field("foo", &self.foo).finish()
240+
Some(ast::FieldList::RecordFieldList(field_list)) => {
241+
let method = make::name_ref("debug_struct");
242+
let mut expr = make::expr_method_call(target, method, args);
243+
for field in field_list.fields() {
244+
let name = field.name()?;
245+
let f_name = make::expr_literal(&(format!("\"{}\"", name))).into();
246+
let f_path = make::expr_path(make::ext::ident_path("self"));
247+
let f_path = make::expr_ref(f_path, false);
248+
let f_path = make::expr_field(f_path, &format!("{}", name)).into();
249+
let args = make::arg_list(vec![f_name, f_path]);
250+
expr = make::expr_method_call(expr, make::name_ref("field"), args);
251+
}
252+
expr
253+
}
254+
255+
// => f.debug_tuple("Name").field(self.0).finish()
256+
Some(ast::FieldList::TupleFieldList(field_list)) => {
257+
let method = make::name_ref("debug_tuple");
258+
let mut expr = make::expr_method_call(target, method, args);
259+
for (idx, _) in field_list.fields().enumerate() {
260+
let f_path = make::expr_path(make::ext::ident_path("self"));
261+
let f_path = make::expr_ref(f_path, false);
262+
let f_path = make::expr_field(f_path, &format!("{}", idx)).into();
263+
let method = make::name_ref("field");
264+
expr = make::expr_method_call(expr, method, make::arg_list(Some(f_path)));
265+
}
266+
expr
267+
}
268+
};
269+
270+
let method = make::name_ref("finish");
271+
let expr = make::expr_method_call(expr, method, make::arg_list(None));
272+
let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
273+
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
274+
Some(())
275+
}
276+
}
277+
}
278+
169279
fn update_attribute(
170280
builder: &mut AssistBuilder,
171281
input: &ast::TokenTree,
@@ -207,41 +317,92 @@ mod tests {
207317
use super::*;
208318

209319
#[test]
210-
fn add_custom_impl_debug() {
320+
fn add_custom_impl_debug_record_struct() {
211321
check_assist(
212322
replace_derive_with_manual_impl,
213323
r#"
214-
mod fmt {
215-
pub struct Error;
216-
pub type Result = Result<(), Error>;
217-
pub struct Formatter<'a>;
218-
pub trait Debug {
219-
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
220-
}
221-
}
222-
324+
//- minicore: fmt
223325
#[derive(Debu$0g)]
224326
struct Foo {
225327
bar: String,
226328
}
227329
"#,
228330
r#"
229-
mod fmt {
230-
pub struct Error;
231-
pub type Result = Result<(), Error>;
232-
pub struct Formatter<'a>;
233-
pub trait Debug {
234-
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
331+
struct Foo {
332+
bar: String,
333+
}
334+
335+
impl core::fmt::Debug for Foo {
336+
$0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
337+
f.debug_struct("Foo").field("bar", &self.bar).finish()
338+
}
339+
}
340+
"#,
341+
)
342+
}
343+
#[test]
344+
fn add_custom_impl_debug_tuple_struct() {
345+
check_assist(
346+
replace_derive_with_manual_impl,
347+
r#"
348+
//- minicore: fmt
349+
#[derive(Debu$0g)]
350+
struct Foo(String, usize);
351+
"#,
352+
r#"struct Foo(String, usize);
353+
354+
impl core::fmt::Debug for Foo {
355+
$0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
356+
f.debug_tuple("Foo").field(&self.0).field(&self.1).finish()
235357
}
236358
}
359+
"#,
360+
)
361+
}
362+
#[test]
363+
fn add_custom_impl_debug_empty_struct() {
364+
check_assist(
365+
replace_derive_with_manual_impl,
366+
r#"
367+
//- minicore: fmt
368+
#[derive(Debu$0g)]
369+
struct Foo;
370+
"#,
371+
r#"
372+
struct Foo;
237373
238-
struct Foo {
239-
bar: String,
374+
impl core::fmt::Debug for Foo {
375+
$0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
376+
f.debug_struct("Foo").finish()
377+
}
378+
}
379+
"#,
380+
)
381+
}
382+
#[test]
383+
fn add_custom_impl_debug_enum() {
384+
check_assist(
385+
replace_derive_with_manual_impl,
386+
r#"
387+
//- minicore: fmt
388+
#[derive(Debu$0g)]
389+
enum Foo {
390+
Bar,
391+
Baz,
392+
}
393+
"#,
394+
r#"
395+
enum Foo {
396+
Bar,
397+
Baz,
240398
}
241399
242-
impl fmt::Debug for Foo {
243-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244-
${0:todo!()}
400+
impl core::fmt::Debug for Foo {
401+
$0fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
402+
match self {
403+
Self::Bar => write!(f, "Bar"),
404+
Self::Baz => write!(f, "Baz"),
405+
}
245406
}
246407
}
247408
"#,

crates/ide_assists/src/tests/generated.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,8 +1385,8 @@ trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
13851385
struct S;
13861386
13871387
impl Debug for S {
1388-
fn fmt(&self, f: &mut Formatter) -> Result<()> {
1389-
${0:todo!()}
1388+
$0fn fmt(&self, f: &mut Formatter) -> Result<()> {
1389+
f.debug_struct("S").finish()
13901390
}
13911391
}
13921392
"#####,

crates/rust-analyzer/tests/slow-tests/tidy.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,13 @@ fn check_todo(path: &Path, text: &str) {
274274
"handlers/add_turbo_fish.rs",
275275
"handlers/generate_function.rs",
276276
"handlers/fill_match_arms.rs",
277+
"handlers/replace_derive_with_manual_impl.rs",
277278
// To support generating `todo!()` in assists, we have `expr_todo()` in
278279
// `ast::make`.
279280
"ast/make.rs",
280281
// The documentation in string literals may contain anything for its own purposes
281282
"ide_db/src/helpers/generated_lints.rs",
283+
"ide_assists/src/tests/generated.rs",
282284
];
283285
if need_todo.iter().any(|p| path.ends_with(p)) {
284286
return;

crates/syntax/src/ast/make.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,13 +311,19 @@ pub fn expr_method_call(
311311
) -> ast::Expr {
312312
expr_from_text(&format!("{}.{}{}", receiver, method, arg_list))
313313
}
314+
pub fn expr_macro_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr {
315+
expr_from_text(&format!("{}!{}", f, arg_list))
316+
}
314317
pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
315318
expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) })
316319
}
317320
pub fn expr_closure(pats: impl IntoIterator<Item = ast::Param>, expr: ast::Expr) -> ast::Expr {
318321
let params = pats.into_iter().join(", ");
319322
expr_from_text(&format!("|{}| {}", params, expr))
320323
}
324+
pub fn expr_field(receiver: ast::Expr, field: &str) -> ast::Expr {
325+
expr_from_text(&format!("{}.{}", receiver, field))
326+
}
321327
pub fn expr_paren(expr: ast::Expr) -> ast::Expr {
322328
expr_from_text(&format!("({})", expr))
323329
}

crates/test_utils/src/minicore.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
//! eq: sized
3232
//! ord: eq, option
3333
//! derive:
34+
//! fmt: result
3435
3536
pub mod marker {
3637
// region:sized
@@ -334,6 +335,17 @@ pub mod cmp {
334335
}
335336
// endregion:eq
336337

338+
// region:fmt
339+
pub mod fmt {
340+
pub struct Error;
341+
pub type Result = Result<(), Error>;
342+
pub struct Formatter<'a>;
343+
pub trait Debug {
344+
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
345+
}
346+
}
347+
// endregion:fmt
348+
337349
// region:slice
338350
pub mod slice {
339351
#[lang = "slice"]

0 commit comments

Comments
 (0)