aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assists/add_missing_impl_members.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/assists/add_missing_impl_members.rs')
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs337
1 files changed, 337 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs
new file mode 100644
index 000000000..2894bdd8a
--- /dev/null
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -0,0 +1,337 @@
1use hir::{db::HirDatabase, HasSource};
2use ra_syntax::{
3 ast::{self, AstNode, NameOwner},
4 SmolStr,
5};
6
7use crate::{ast_builder::AstBuilder, ast_editor::AstEditor, Assist, AssistCtx, AssistId};
8
9#[derive(PartialEq)]
10enum AddMissingImplMembersMode {
11 DefaultMethodsOnly,
12 NoDefaultMethods,
13}
14
15pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
16 add_missing_impl_members_inner(
17 ctx,
18 AddMissingImplMembersMode::NoDefaultMethods,
19 "add_impl_missing_members",
20 "add missing impl members",
21 )
22}
23
24pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
25 add_missing_impl_members_inner(
26 ctx,
27 AddMissingImplMembersMode::DefaultMethodsOnly,
28 "add_impl_default_members",
29 "add impl default members",
30 )
31}
32
33fn add_missing_impl_members_inner(
34 mut ctx: AssistCtx<impl HirDatabase>,
35 mode: AddMissingImplMembersMode,
36 assist_id: &'static str,
37 label: &'static str,
38) -> Option<Assist> {
39 let impl_node = ctx.node_at_offset::<ast::ImplBlock>()?;
40 let impl_item_list = impl_node.item_list()?;
41
42 let trait_def = {
43 let file_id = ctx.frange.file_id;
44 let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, impl_node.syntax(), None);
45
46 resolve_target_trait_def(ctx.db, &analyzer, &impl_node)?
47 };
48
49 let def_name = |item: &ast::ImplItem| -> Option<SmolStr> {
50 match item {
51 ast::ImplItem::FnDef(def) => def.name(),
52 ast::ImplItem::TypeAliasDef(def) => def.name(),
53 ast::ImplItem::ConstDef(def) => def.name(),
54 }
55 .map(|it| it.text().clone())
56 };
57
58 let trait_items = trait_def.item_list()?.impl_items();
59 let impl_items = impl_item_list.impl_items().collect::<Vec<_>>();
60
61 let missing_items: Vec<_> = trait_items
62 .filter(|t| def_name(t).is_some())
63 .filter(|t| match t {
64 ast::ImplItem::FnDef(def) => match mode {
65 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
66 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
67 },
68 _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
69 })
70 .filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t)))
71 .collect();
72 if missing_items.is_empty() {
73 return None;
74 }
75
76 ctx.add_action(AssistId(assist_id), label, |edit| {
77 let n_existing_items = impl_item_list.impl_items().count();
78 let items = missing_items.into_iter().map(|it| match it {
79 ast::ImplItem::FnDef(def) => strip_docstring(add_body(def).into()),
80 _ => strip_docstring(it),
81 });
82 let mut ast_editor = AstEditor::new(impl_item_list);
83
84 ast_editor.append_items(items);
85
86 let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap();
87 let cursor_position = first_new_item.syntax().text_range().start();
88 ast_editor.into_text_edit(edit.text_edit_builder());
89
90 edit.set_cursor(cursor_position);
91 });
92
93 ctx.build()
94}
95
96fn strip_docstring(item: ast::ImplItem) -> ast::ImplItem {
97 let mut ast_editor = AstEditor::new(item);
98 ast_editor.strip_attrs_and_docs();
99 ast_editor.ast().to_owned()
100}
101
102fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
103 let mut ast_editor = AstEditor::new(fn_def.clone());
104 if fn_def.body().is_none() {
105 ast_editor.set_body(&AstBuilder::<ast::Block>::single_expr(
106 &AstBuilder::<ast::Expr>::unimplemented(),
107 ));
108 }
109 ast_editor.ast().to_owned()
110}
111
112/// Given an `ast::ImplBlock`, resolves the target trait (the one being
113/// implemented) to a `ast::TraitDef`.
114fn resolve_target_trait_def(
115 db: &impl HirDatabase,
116 analyzer: &hir::SourceAnalyzer,
117 impl_block: &ast::ImplBlock,
118) -> Option<ast::TraitDef> {
119 let ast_path = impl_block
120 .target_trait()
121 .map(|it| it.syntax().clone())
122 .and_then(ast::PathType::cast)?
123 .path()?;
124
125 match analyzer.resolve_path(db, &ast_path) {
126 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).ast),
127 _ => None,
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::helpers::{check_assist, check_assist_not_applicable};
135
136 #[test]
137 fn test_add_missing_impl_members() {
138 check_assist(
139 add_missing_impl_members,
140 "
141trait Foo {
142 type Output;
143
144 const CONST: usize = 42;
145
146 fn foo(&self);
147 fn bar(&self);
148 fn baz(&self);
149}
150
151struct S;
152
153impl Foo for S {
154 fn bar(&self) {}
155<|>
156}",
157 "
158trait Foo {
159 type Output;
160
161 const CONST: usize = 42;
162
163 fn foo(&self);
164 fn bar(&self);
165 fn baz(&self);
166}
167
168struct S;
169
170impl Foo for S {
171 fn bar(&self) {}
172 <|>type Output;
173 const CONST: usize = 42;
174 fn foo(&self) { unimplemented!() }
175 fn baz(&self) { unimplemented!() }
176
177}",
178 );
179 }
180
181 #[test]
182 fn test_copied_overriden_members() {
183 check_assist(
184 add_missing_impl_members,
185 "
186trait Foo {
187 fn foo(&self);
188 fn bar(&self) -> bool { true }
189 fn baz(&self) -> u32 { 42 }
190}
191
192struct S;
193
194impl Foo for S {
195 fn bar(&self) {}
196<|>
197}",
198 "
199trait Foo {
200 fn foo(&self);
201 fn bar(&self) -> bool { true }
202 fn baz(&self) -> u32 { 42 }
203}
204
205struct S;
206
207impl Foo for S {
208 fn bar(&self) {}
209 <|>fn foo(&self) { unimplemented!() }
210
211}",
212 );
213 }
214
215 #[test]
216 fn test_empty_impl_block() {
217 check_assist(
218 add_missing_impl_members,
219 "
220trait Foo { fn foo(&self); }
221struct S;
222impl Foo for S { <|> }",
223 "
224trait Foo { fn foo(&self); }
225struct S;
226impl Foo for S {
227 <|>fn foo(&self) { unimplemented!() }
228}",
229 );
230 }
231
232 #[test]
233 fn test_cursor_after_empty_impl_block() {
234 check_assist(
235 add_missing_impl_members,
236 "
237trait Foo { fn foo(&self); }
238struct S;
239impl Foo for S {}<|>",
240 "
241trait Foo { fn foo(&self); }
242struct S;
243impl Foo for S {
244 <|>fn foo(&self) { unimplemented!() }
245}",
246 )
247 }
248
249 #[test]
250 fn test_empty_trait() {
251 check_assist_not_applicable(
252 add_missing_impl_members,
253 "
254trait Foo;
255struct S;
256impl Foo for S { <|> }",
257 )
258 }
259
260 #[test]
261 fn test_ignore_unnamed_trait_members_and_default_methods() {
262 check_assist_not_applicable(
263 add_missing_impl_members,
264 "
265trait Foo {
266 fn (arg: u32);
267 fn valid(some: u32) -> bool { false }
268}
269struct S;
270impl Foo for S { <|> }",
271 )
272 }
273
274 #[test]
275 fn test_with_docstring_and_attrs() {
276 check_assist(
277 add_missing_impl_members,
278 r#"
279#[doc(alias = "test alias")]
280trait Foo {
281 /// doc string
282 type Output;
283
284 #[must_use]
285 fn foo(&self);
286}
287struct S;
288impl Foo for S {}<|>"#,
289 r#"
290#[doc(alias = "test alias")]
291trait Foo {
292 /// doc string
293 type Output;
294
295 #[must_use]
296 fn foo(&self);
297}
298struct S;
299impl Foo for S {
300 <|>type Output;
301 fn foo(&self) { unimplemented!() }
302}"#,
303 )
304 }
305
306 #[test]
307 fn test_default_methods() {
308 check_assist(
309 add_missing_default_members,
310 "
311trait Foo {
312 type Output;
313
314 const CONST: usize = 42;
315
316 fn valid(some: u32) -> bool { false }
317 fn foo(some: u32) -> bool;
318}
319struct S;
320impl Foo for S { <|> }",
321 "
322trait Foo {
323 type Output;
324
325 const CONST: usize = 42;
326
327 fn valid(some: u32) -> bool { false }
328 fn foo(some: u32) -> bool;
329}
330struct S;
331impl Foo for S {
332 <|>fn valid(some: u32) -> bool { false }
333}",
334 )
335 }
336
337}