Skip to content

Commit e652545

Browse files
Merge #9830
9830: Enable more assists to generate default trait body impls r=Veykril a=yoshuawuyts Enable more assists to benefit from trait body generation. Follow-up to #9825 and #9814. __edit:__ I'd like to move the existing tests to this new file too, but I'll do that in a follow-up PR. Co-authored-by: Yoshua Wuyts <yoshuawuyts@gmail.com>
2 parents 2bd28c6 + 3268907 commit e652545

File tree

6 files changed

+226
-161
lines changed

6 files changed

+226
-161
lines changed

crates/hir/src/has_source.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use hir_expand::InFile;
1010
use syntax::ast;
1111

1212
use crate::{
13-
db::HirDatabase, Const, ConstParam, Enum, Field, FieldSource, Function, Impl, LifetimeParam,
14-
MacroDef, Module, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant,
13+
db::HirDatabase, Adt, Const, ConstParam, Enum, Field, FieldSource, Function, Impl,
14+
LifetimeParam, MacroDef, Module, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant,
1515
};
1616

1717
pub trait HasSource {
@@ -56,6 +56,16 @@ impl HasSource for Field {
5656
Some(field_source)
5757
}
5858
}
59+
impl HasSource for Adt {
60+
type Ast = ast::Adt;
61+
fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
62+
match self {
63+
Adt::Struct(s) => Some(s.source(db)?.map(|s| ast::Adt::Struct(s))),
64+
Adt::Union(u) => Some(u.source(db)?.map(|u| ast::Adt::Union(u))),
65+
Adt::Enum(e) => Some(e.source(db)?.map(|e| ast::Adt::Enum(e))),
66+
}
67+
}
68+
}
5969
impl HasSource for Struct {
6070
type Ast = ast::Struct;
6171
fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {

crates/ide_assists/src/handlers/add_missing_impl_members.rs

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
use hir::HasSource;
12
use ide_db::traits::resolve_target_trait;
2-
use syntax::ast::{self, AstNode};
3+
use syntax::ast::{self, make, AstNode};
34

45
use crate::{
56
assist_context::{AssistContext, Assists},
67
utils::{
7-
add_trait_assoc_items_to_impl, filter_assoc_items, render_snippet, Cursor, DefaultMethods,
8+
add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, render_snippet,
9+
Cursor, DefaultMethods,
810
},
911
AssistId, AssistKind,
1012
};
@@ -115,18 +117,26 @@ fn add_missing_impl_members_inner(
115117
let target = impl_def.syntax().text_range();
116118
acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
117119
let target_scope = ctx.sema.scope(impl_def.syntax());
118-
let (new_impl_def, first_new_item) =
119-
add_trait_assoc_items_to_impl(&ctx.sema, missing_items, trait_, impl_def, target_scope);
120+
let (new_impl_def, first_new_item) = add_trait_assoc_items_to_impl(
121+
&ctx.sema,
122+
missing_items,
123+
trait_,
124+
impl_def.clone(),
125+
target_scope,
126+
);
120127
match ctx.config.snippet_cap {
121128
None => builder.replace(target, new_impl_def.to_string()),
122129
Some(cap) => {
123130
let mut cursor = Cursor::Before(first_new_item.syntax());
124131
let placeholder;
125132
if let ast::AssocItem::Fn(func) = &first_new_item {
126-
if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
127-
if m.syntax().text() == "todo!()" {
128-
placeholder = m;
129-
cursor = Cursor::Replace(placeholder.syntax());
133+
if try_gen_trait_body(ctx, func, &trait_, &impl_def).is_none() {
134+
if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
135+
{
136+
if m.syntax().text() == "todo!()" {
137+
placeholder = m;
138+
cursor = Cursor::Replace(placeholder.syntax());
139+
}
130140
}
131141
}
132142
}
@@ -140,6 +150,18 @@ fn add_missing_impl_members_inner(
140150
})
141151
}
142152

153+
fn try_gen_trait_body(
154+
ctx: &AssistContext,
155+
func: &ast::Fn,
156+
trait_: &hir::Trait,
157+
impl_def: &ast::Impl,
158+
) -> Option<()> {
159+
let trait_path = make::ext::ident_path(&trait_.name(ctx.db()).to_string());
160+
let hir_ty = ctx.sema.resolve_type(&impl_def.self_ty()?)?;
161+
let adt = hir_ty.as_adt()?.source(ctx.db())?;
162+
gen_trait_fn_body(func, &trait_path, &adt.value)
163+
}
164+
143165
#[cfg(test)]
144166
mod tests {
145167
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -847,4 +869,28 @@ impl T for () {
847869
",
848870
);
849871
}
872+
873+
#[test]
874+
fn test_default_body_generation() {
875+
check_assist(
876+
add_missing_impl_members,
877+
r#"
878+
//- minicore: default
879+
struct Foo(usize);
880+
881+
impl Default for Foo {
882+
$0
883+
}
884+
"#,
885+
r#"
886+
struct Foo(usize);
887+
888+
impl Default for Foo {
889+
$0fn default() -> Self {
890+
Self(Default::default())
891+
}
892+
}
893+
"#,
894+
)
895+
}
850896
}

crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs

Lines changed: 3 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ 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;
75
use syntax::{
86
ast::{self, make, AstNode, NameOwner},
97
SyntaxKind::{IDENT, WHITESPACE},
@@ -12,8 +10,8 @@ use syntax::{
1210
use crate::{
1311
assist_context::{AssistBuilder, AssistContext, Assists},
1412
utils::{
15-
add_trait_assoc_items_to_impl, filter_assoc_items, generate_trait_impl_text,
16-
render_snippet, Cursor, DefaultMethods,
13+
add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body,
14+
generate_trait_impl_text, render_snippet, Cursor, DefaultMethods,
1715
},
1816
AssistId, AssistKind,
1917
};
@@ -169,158 +167,12 @@ fn impl_def_from_trait(
169167

170168
// Generate a default `impl` function body for the derived trait.
171169
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
172-
let _ = gen_trait_body_impl(func, trait_path, adt, annotated_name);
170+
let _ = gen_trait_fn_body(func, trait_path, adt);
173171
};
174172

175173
Some((impl_def, first_assoc_item))
176174
}
177175

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_trait_body_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-
"Default" => gen_default_impl(adt, func),
192-
_ => None,
193-
}
194-
}
195-
196-
/// Generate a `Debug` impl based on the fields and members of the target type.
197-
fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn, annotated_name: &ast::Name) -> Option<()> {
198-
match adt {
199-
// `Debug` cannot be derived for unions, so no default impl can be provided.
200-
ast::Adt::Union(_) => None,
201-
202-
// => match self { Self::Variant => write!(f, "Variant") }
203-
ast::Adt::Enum(enum_) => {
204-
let list = enum_.variant_list()?;
205-
let mut arms = vec![];
206-
for variant in list.variants() {
207-
let name = variant.name()?;
208-
let left = make::ext::ident_path("Self");
209-
let right = make::ext::ident_path(&format!("{}", name));
210-
let variant_name = make::path_pat(make::path_concat(left, right));
211-
212-
let target = make::expr_path(make::ext::ident_path("f").into());
213-
let fmt_string = make::expr_literal(&(format!("\"{}\"", name))).into();
214-
let args = make::arg_list(vec![target, fmt_string]);
215-
let macro_name = make::expr_path(make::ext::ident_path("write"));
216-
let macro_call = make::expr_macro_call(macro_name, args);
217-
218-
arms.push(make::match_arm(Some(variant_name.into()), None, macro_call.into()));
219-
}
220-
221-
let match_target = make::expr_path(make::ext::ident_path("self"));
222-
let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1));
223-
let match_expr = make::expr_match(match_target, list);
224-
225-
let body = make::block_expr(None, Some(match_expr));
226-
let body = body.indent(ast::edit::IndentLevel(1));
227-
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
228-
Some(())
229-
}
230-
231-
ast::Adt::Struct(strukt) => {
232-
let name = format!("\"{}\"", annotated_name);
233-
let args = make::arg_list(Some(make::expr_literal(&name).into()));
234-
let target = make::expr_path(make::ext::ident_path("f"));
235-
236-
let expr = match strukt.field_list() {
237-
// => f.debug_struct("Name").finish()
238-
None => make::expr_method_call(target, make::name_ref("debug_struct"), args),
239-
240-
// => f.debug_struct("Name").field("foo", &self.foo).finish()
241-
Some(ast::FieldList::RecordFieldList(field_list)) => {
242-
let method = make::name_ref("debug_struct");
243-
let mut expr = make::expr_method_call(target, method, args);
244-
for field in field_list.fields() {
245-
let name = field.name()?;
246-
let f_name = make::expr_literal(&(format!("\"{}\"", name))).into();
247-
let f_path = make::expr_path(make::ext::ident_path("self"));
248-
let f_path = make::expr_ref(f_path, false);
249-
let f_path = make::expr_field(f_path, &format!("{}", name)).into();
250-
let args = make::arg_list(vec![f_name, f_path]);
251-
expr = make::expr_method_call(expr, make::name_ref("field"), args);
252-
}
253-
expr
254-
}
255-
256-
// => f.debug_tuple("Name").field(self.0).finish()
257-
Some(ast::FieldList::TupleFieldList(field_list)) => {
258-
let method = make::name_ref("debug_tuple");
259-
let mut expr = make::expr_method_call(target, method, args);
260-
for (idx, _) in field_list.fields().enumerate() {
261-
let f_path = make::expr_path(make::ext::ident_path("self"));
262-
let f_path = make::expr_ref(f_path, false);
263-
let f_path = make::expr_field(f_path, &format!("{}", idx)).into();
264-
let method = make::name_ref("field");
265-
expr = make::expr_method_call(expr, method, make::arg_list(Some(f_path)));
266-
}
267-
expr
268-
}
269-
};
270-
271-
let method = make::name_ref("finish");
272-
let expr = make::expr_method_call(expr, method, make::arg_list(None));
273-
let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
274-
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
275-
Some(())
276-
}
277-
}
278-
}
279-
280-
/// Generate a `Debug` impl based on the fields and members of the target type.
281-
fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
282-
fn gen_default_call() -> ast::Expr {
283-
let trait_name = make::ext::ident_path("Default");
284-
let method_name = make::ext::ident_path("default");
285-
let fn_name = make::expr_path(make::path_concat(trait_name, method_name));
286-
make::expr_call(fn_name, make::arg_list(None))
287-
}
288-
match adt {
289-
// `Debug` cannot be derived for unions, so no default impl can be provided.
290-
ast::Adt::Union(_) => None,
291-
// Deriving `Debug` for enums is not stable yet.
292-
ast::Adt::Enum(_) => None,
293-
ast::Adt::Struct(strukt) => {
294-
let expr = match strukt.field_list() {
295-
Some(ast::FieldList::RecordFieldList(field_list)) => {
296-
let mut fields = vec![];
297-
for field in field_list.fields() {
298-
let method_call = gen_default_call();
299-
let name_ref = make::name_ref(&field.name()?.to_string());
300-
let field = make::record_expr_field(name_ref, Some(method_call));
301-
fields.push(field);
302-
}
303-
let struct_name = make::ext::ident_path("Self");
304-
let fields = make::record_expr_field_list(fields);
305-
make::record_expr(struct_name, fields).into()
306-
}
307-
Some(ast::FieldList::TupleFieldList(field_list)) => {
308-
let struct_name = make::expr_path(make::ext::ident_path("Self"));
309-
let fields = field_list.fields().map(|_| gen_default_call());
310-
make::expr_call(struct_name, make::arg_list(fields))
311-
}
312-
None => {
313-
let struct_name = make::ext::ident_path("Self");
314-
let fields = make::record_expr_field_list(None);
315-
make::record_expr(struct_name, fields).into()
316-
}
317-
};
318-
let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
319-
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax());
320-
Some(())
321-
}
322-
}
323-
}
324176
fn update_attribute(
325177
builder: &mut AssistBuilder,
326178
input: &ast::TokenTree,

crates/ide_assists/src/utils.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Assorted functions shared by several assists.
22
33
pub(crate) mod suggest_name;
4+
mod gen_trait_fn_body;
45

56
use std::ops;
67

@@ -25,6 +26,8 @@ use syntax::{
2526

2627
use crate::assist_context::{AssistBuilder, AssistContext};
2728

29+
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
30+
2831
pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
2932
extract_trivial_expression(&block)
3033
.filter(|expr| !expr.syntax().text().contains_char('\n'))

0 commit comments

Comments
 (0)