Skip to content

Commit d6b788a

Browse files
committed
Add trait codegen to add_missing_impl_members assist
1 parent 4d2e25a commit d6b788a

File tree

5 files changed

+74
-19
lines changed

5 files changed

+74
-19
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_body, render_snippet, Cursor,
9+
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::path_from_text(&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_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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ fn impl_def_from_trait(
168168

169169
// Generate a default `impl` function body for the derived trait.
170170
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
171-
let _ = gen_trait_body(func, trait_path, adt, annotated_name);
171+
let _ = gen_trait_body(func, trait_path, adt);
172172
};
173173

174174
Some((impl_def, first_assoc_item))

crates/ide_assists/src/utils/gen_trait_body.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! This module contains functions to generate default trait impl function bodies where possible.
2+
13
use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, NameOwner};
24
use syntax::ted;
35

@@ -6,21 +8,17 @@ use syntax::ted;
68
/// Returns `Option` so that we can use `?` rather than `if let Some`. Returning
79
/// `None` means that generating a custom trait body failed, and the body will remain
810
/// as `todo!` instead.
9-
pub(crate) fn gen_trait_body(
10-
func: &ast::Fn,
11-
trait_path: &ast::Path,
12-
adt: &ast::Adt,
13-
annotated_name: &ast::Name,
14-
) -> Option<()> {
11+
pub(crate) fn gen_trait_body(func: &ast::Fn, trait_path: &ast::Path, adt: &ast::Adt) -> Option<()> {
1512
match trait_path.segment()?.name_ref()?.text().as_str() {
16-
"Debug" => gen_debug_impl(adt, func, annotated_name),
13+
"Debug" => gen_debug_impl(adt, func),
1714
"Default" => gen_default_impl(adt, func),
1815
_ => None,
1916
}
2017
}
2118

2219
/// Generate a `Debug` impl based on the fields and members of the target type.
23-
fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn, annotated_name: &ast::Name) -> Option<()> {
20+
fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
21+
let annotated_name = adt.name()?;
2422
match adt {
2523
// `Debug` cannot be derived for unions, so no default impl can be provided.
2624
ast::Adt::Union(_) => None,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ fn check_todo(path: &Path, text: &str) {
280280
"ast/make.rs",
281281
// The documentation in string literals may contain anything for its own purposes
282282
"ide_db/src/helpers/generated_lints.rs",
283+
"ide_assists/src/utils/gen_trait_body.rs",
283284
"ide_assists/src/tests/generated.rs",
284285
];
285286
if need_todo.iter().any(|p| path.ends_with(p)) {

0 commit comments

Comments
 (0)