Skip to content

Commit dac7060

Browse files
bors[bot]Veykril
andauthored
Merge #6476
6476: Add missing AssocItems in add_custom_impl assist r=matklad a=Veykril ```rust use std::fmt; #[derive(Debu<|>g)] struct Foo { bar: String, } ``` -> ```rust use std::fmt; struct Foo { bar: String, } impl fmt::Debug for Foo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ${0:todo!()} } } ``` Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
2 parents f3fe656 + 19443c1 commit dac7060

File tree

5 files changed

+240
-110
lines changed

5 files changed

+240
-110
lines changed

crates/assists/src/handlers/add_custom_impl.rs

Lines changed: 131 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ use syntax::{
44
ast::{self, make, AstNode},
55
Direction, SmolStr,
66
SyntaxKind::{IDENT, WHITESPACE},
7-
TextRange, TextSize,
7+
TextSize,
88
};
99

1010
use crate::{
11-
assist_config::SnippetCap,
1211
assist_context::{AssistBuilder, AssistContext, Assists},
13-
utils::mod_path_to_ast,
12+
utils::{
13+
add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor,
14+
DefaultMethods,
15+
},
1416
AssistId, AssistKind,
1517
};
1618

@@ -47,11 +49,10 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
4749
ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
4850
let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
4951

50-
let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
51-
let annotated_name = annotated.syntax().text().to_string();
52-
let insert_pos = annotated.syntax().parent()?.text_range().end();
52+
let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
53+
let insert_pos = annotated_name.syntax().parent()?.text_range().end();
5354

54-
let current_module = ctx.sema.scope(annotated.syntax()).module()?;
55+
let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
5556
let current_crate = current_module.krate();
5657

5758
let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
@@ -69,21 +70,22 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
6970
});
7071

7172
let mut no_traits_found = true;
72-
for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) {
73-
add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
73+
for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
74+
add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?;
7475
}
7576
if no_traits_found {
76-
add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?;
77+
add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?;
7778
}
7879
Some(())
7980
}
8081

8182
fn add_assist(
8283
acc: &mut Assists,
83-
snippet_cap: Option<SnippetCap>,
84+
ctx: &AssistContext,
8485
attr: &ast::Attr,
8586
trait_path: &ast::Path,
86-
annotated_name: &str,
87+
trait_: Option<hir::Trait>,
88+
annotated_name: &ast::Name,
8789
insert_pos: TextSize,
8890
) -> Option<()> {
8991
let target = attr.syntax().text_range();
@@ -92,25 +94,62 @@ fn add_assist(
9294
let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
9395

9496
acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
97+
let impl_def_with_items =
98+
impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path);
9599
update_attribute(builder, &input, &trait_name, &attr);
96-
match snippet_cap {
97-
Some(cap) => {
100+
match (ctx.config.snippet_cap, impl_def_with_items) {
101+
(None, _) => builder.insert(
102+
insert_pos,
103+
format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
104+
),
105+
(Some(cap), None) => builder.insert_snippet(
106+
cap,
107+
insert_pos,
108+
format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
109+
),
110+
(Some(cap), Some((impl_def, first_assoc_item))) => {
111+
let mut cursor = Cursor::Before(first_assoc_item.syntax());
112+
let placeholder;
113+
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
114+
if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
115+
if m.syntax().text() == "todo!()" {
116+
placeholder = m;
117+
cursor = Cursor::Replace(placeholder.syntax());
118+
}
119+
}
120+
}
121+
98122
builder.insert_snippet(
99123
cap,
100124
insert_pos,
101-
format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
102-
);
103-
}
104-
None => {
105-
builder.insert(
106-
insert_pos,
107-
format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
108-
);
125+
format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)),
126+
)
109127
}
110-
}
128+
};
111129
})
112130
}
113131

132+
fn impl_def_from_trait(
133+
sema: &hir::Semantics<ide_db::RootDatabase>,
134+
annotated_name: &ast::Name,
135+
trait_: Option<hir::Trait>,
136+
trait_path: &ast::Path,
137+
) -> Option<(ast::Impl, ast::AssocItem)> {
138+
let trait_ = trait_?;
139+
let target_scope = sema.scope(annotated_name.syntax());
140+
let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No);
141+
if trait_items.is_empty() {
142+
return None;
143+
}
144+
let impl_def = make::impl_trait(
145+
trait_path.clone(),
146+
make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))),
147+
);
148+
let (impl_def, first_assoc_item) =
149+
add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
150+
Some((impl_def, first_assoc_item))
151+
}
152+
114153
fn update_attribute(
115154
builder: &mut AssistBuilder,
116155
input: &ast::TokenTree,
@@ -133,13 +172,14 @@ fn update_attribute(
133172
let attr_range = attr.syntax().text_range();
134173
builder.delete(attr_range);
135174

136-
let line_break_range = attr
175+
if let Some(line_break_range) = attr
137176
.syntax()
138177
.next_sibling_or_token()
139178
.filter(|t| t.kind() == WHITESPACE)
140179
.map(|t| t.text_range())
141-
.unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
142-
builder.delete(line_break_range);
180+
{
181+
builder.delete(line_break_range);
182+
}
143183
}
144184
}
145185

@@ -150,12 +190,17 @@ mod tests {
150190
use super::*;
151191

152192
#[test]
153-
fn add_custom_impl_qualified() {
193+
fn add_custom_impl_debug() {
154194
check_assist(
155195
add_custom_impl,
156196
"
157197
mod fmt {
158-
pub trait Debug {}
198+
pub struct Error;
199+
pub type Result = Result<(), Error>;
200+
pub struct Formatter<'a>;
201+
pub trait Debug {
202+
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
203+
}
159204
}
160205
161206
#[derive(Debu<|>g)]
@@ -165,15 +210,71 @@ struct Foo {
165210
",
166211
"
167212
mod fmt {
168-
pub trait Debug {}
213+
pub struct Error;
214+
pub type Result = Result<(), Error>;
215+
pub struct Formatter<'a>;
216+
pub trait Debug {
217+
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
218+
}
169219
}
170220
171221
struct Foo {
172222
bar: String,
173223
}
174224
175225
impl fmt::Debug for Foo {
176-
$0
226+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227+
${0:todo!()}
228+
}
229+
}
230+
",
231+
)
232+
}
233+
#[test]
234+
fn add_custom_impl_all() {
235+
check_assist(
236+
add_custom_impl,
237+
"
238+
mod foo {
239+
pub trait Bar {
240+
type Qux;
241+
const Baz: usize = 42;
242+
const Fez: usize;
243+
fn foo();
244+
fn bar() {}
245+
}
246+
}
247+
248+
#[derive(<|>Bar)]
249+
struct Foo {
250+
bar: String,
251+
}
252+
",
253+
"
254+
mod foo {
255+
pub trait Bar {
256+
type Qux;
257+
const Baz: usize = 42;
258+
const Fez: usize;
259+
fn foo();
260+
fn bar() {}
261+
}
262+
}
263+
264+
struct Foo {
265+
bar: String,
266+
}
267+
268+
impl foo::Bar for Foo {
269+
$0type Qux;
270+
271+
const Baz: usize = 42;
272+
273+
const Fez: usize;
274+
275+
fn foo() {
276+
todo!()
277+
}
177278
}
178279
",
179280
)

crates/assists/src/handlers/add_missing_impl_members.rs

Lines changed: 15 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,14 @@
1-
use hir::HasSource;
2-
use ide_db::traits::{get_missing_assoc_items, resolve_target_trait};
3-
use syntax::{
4-
ast::{
5-
self,
6-
edit::{self, AstNodeEdit, IndentLevel},
7-
make, AstNode, NameOwner,
8-
},
9-
SmolStr,
10-
};
1+
use ide_db::traits::resolve_target_trait;
2+
use syntax::ast::{self, AstNode};
113

124
use crate::{
135
assist_context::{AssistContext, Assists},
14-
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
15-
utils::{render_snippet, Cursor},
6+
utils::add_trait_assoc_items_to_impl,
7+
utils::DefaultMethods,
8+
utils::{filter_assoc_items, render_snippet, Cursor},
169
AssistId, AssistKind,
1710
};
1811

19-
#[derive(PartialEq)]
20-
enum AddMissingImplMembersMode {
21-
DefaultMethodsOnly,
22-
NoDefaultMethods,
23-
}
24-
2512
// Assist: add_impl_missing_members
2613
//
2714
// Adds scaffold for required impl members.
@@ -55,7 +42,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
5542
add_missing_impl_members_inner(
5643
acc,
5744
ctx,
58-
AddMissingImplMembersMode::NoDefaultMethods,
45+
DefaultMethods::No,
5946
"add_impl_missing_members",
6047
"Implement missing members",
6148
)
@@ -97,7 +84,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext
9784
add_missing_impl_members_inner(
9885
acc,
9986
ctx,
100-
AddMissingImplMembersMode::DefaultMethodsOnly,
87+
DefaultMethods::Only,
10188
"add_impl_default_members",
10289
"Implement default members",
10390
)
@@ -106,70 +93,29 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext
10693
fn add_missing_impl_members_inner(
10794
acc: &mut Assists,
10895
ctx: &AssistContext,
109-
mode: AddMissingImplMembersMode,
96+
mode: DefaultMethods,
11097
assist_id: &'static str,
11198
label: &'static str,
11299
) -> Option<()> {
113100
let _p = profile::span("add_missing_impl_members_inner");
114101
let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
115102
let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
116103

117-
let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
118-
match item {
119-
ast::AssocItem::Fn(def) => def.name(),
120-
ast::AssocItem::TypeAlias(def) => def.name(),
121-
ast::AssocItem::Const(def) => def.name(),
122-
ast::AssocItem::MacroCall(_) => None,
123-
}
124-
.map(|it| it.text().clone())
125-
};
126-
127-
let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
128-
.iter()
129-
.map(|i| match i {
130-
hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
131-
hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
132-
hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
133-
})
134-
.filter(|t| def_name(&t).is_some())
135-
.filter(|t| match t {
136-
ast::AssocItem::Fn(def) => match mode {
137-
AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
138-
AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
139-
},
140-
_ => mode == AddMissingImplMembersMode::NoDefaultMethods,
141-
})
142-
.collect::<Vec<_>>();
104+
let missing_items = filter_assoc_items(
105+
ctx.db(),
106+
&ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def),
107+
mode,
108+
);
143109

144110
if missing_items.is_empty() {
145111
return None;
146112
}
147113

148114
let target = impl_def.syntax().text_range();
149115
acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
150-
let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
151-
152-
let n_existing_items = impl_item_list.assoc_items().count();
153-
let source_scope = ctx.sema.scope_for_def(trait_);
154116
let target_scope = ctx.sema.scope(impl_def.syntax());
155-
let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
156-
.or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
157-
158-
let items = missing_items
159-
.into_iter()
160-
.map(|it| ast_transform::apply(&*ast_transform, it))
161-
.map(|it| match it {
162-
ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
163-
ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
164-
_ => it,
165-
})
166-
.map(|it| edit::remove_attrs_and_docs(&it));
167-
168-
let new_impl_item_list = impl_item_list.append_items(items);
169-
let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
170-
let first_new_item =
171-
new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
172-
117+
let (new_impl_def, first_new_item) =
118+
add_trait_assoc_items_to_impl(&ctx.sema, missing_items, trait_, impl_def, target_scope);
173119
match ctx.config.snippet_cap {
174120
None => builder.replace(target, new_impl_def.to_string()),
175121
Some(cap) => {
@@ -193,14 +139,6 @@ fn add_missing_impl_members_inner(
193139
})
194140
}
195141

196-
fn add_body(fn_def: ast::Fn) -> ast::Fn {
197-
if fn_def.body().is_some() {
198-
return fn_def;
199-
}
200-
let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
201-
fn_def.with_body(body)
202-
}
203-
204142
#[cfg(test)]
205143
mod tests {
206144
use crate::tests::{check_assist, check_assist_not_applicable};

0 commit comments

Comments
 (0)