aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-03-17 08:51:06 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-03-17 08:51:06 +0000
commit65e763fa84ae70ec9cee13f434acaae5371ad8e5 (patch)
treeb60ae6575b9ca697a35788a4555c21c1d359b3c0 /crates
parent9a59272f5f3efd502f666c78ce901fff8db0dfba (diff)
parent30a226c72532f04033d4c283ba7c94790e2da541 (diff)
Merge #947
947: Add missing impl members r=matklad a=Xanewok Closes #878. This took longer than expected as I wrapped my head around the API and the project - hopefully I didn't miss any edge case here. r? @matklad Co-authored-by: Igor Matuszewski <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/add_missing_impl_members.rs283
-rw-r--r--crates/ra_assists/src/lib.rs2
2 files changed, 285 insertions, 0 deletions
diff --git a/crates/ra_assists/src/add_missing_impl_members.rs b/crates/ra_assists/src/add_missing_impl_members.rs
new file mode 100644
index 000000000..4435c4b5d
--- /dev/null
+++ b/crates/ra_assists/src/add_missing_impl_members.rs
@@ -0,0 +1,283 @@
1use crate::{Assist, AssistId, AssistCtx};
2
3use hir::Resolver;
4use hir::db::HirDatabase;
5use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc};
6use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner};
7use ra_db::FilePosition;
8use ra_fmt::{leading_indent, reindent};
9
10use itertools::Itertools;
11
12pub(crate) fn add_missing_impl_members(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
13 let impl_node = ctx.node_at_offset::<ast::ImplBlock>()?;
14 let impl_item_list = impl_node.item_list()?;
15
16 let trait_def = {
17 let file_id = ctx.frange.file_id;
18 let position = FilePosition { file_id, offset: impl_node.syntax().range().start() };
19 let resolver = hir::source_binder::resolver_for_position(ctx.db, position);
20
21 resolve_target_trait_def(ctx.db, &resolver, impl_node)?
22 };
23
24 let missing_fns: Vec<_> = {
25 let fn_def_opt = |kind| if let ImplItemKind::FnDef(def) = kind { Some(def) } else { None };
26 let def_name = |def| -> Option<&SmolStr> { FnDef::name(def).map(ast::Name::text) };
27
28 let trait_items =
29 trait_def.syntax().descendants().find_map(ast::ItemList::cast)?.impl_items();
30 let impl_items = impl_item_list.impl_items();
31
32 let trait_fns = trait_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>();
33 let impl_fns = impl_items.map(ImplItem::kind).filter_map(fn_def_opt).collect::<Vec<_>>();
34
35 trait_fns
36 .into_iter()
37 .filter(|t| def_name(t).is_some())
38 .filter(|t| impl_fns.iter().all(|i| def_name(i) != def_name(t)))
39 .collect()
40 };
41 if missing_fns.is_empty() {
42 return None;
43 }
44
45 ctx.add_action(AssistId("add_impl_missing_members"), "add missing impl members", |edit| {
46 let (parent_indent, indent) = {
47 // FIXME: Find a way to get the indent already used in the file.
48 // Now, we copy the indent of first item or indent with 4 spaces relative to impl block
49 const DEFAULT_INDENT: &str = " ";
50 let first_item = impl_item_list.impl_items().next();
51 let first_item_indent =
52 first_item.and_then(|i| leading_indent(i.syntax())).map(ToOwned::to_owned);
53 let impl_block_indent = leading_indent(impl_node.syntax()).unwrap_or_default();
54
55 (
56 impl_block_indent.to_owned(),
57 first_item_indent.unwrap_or_else(|| impl_block_indent.to_owned() + DEFAULT_INDENT),
58 )
59 };
60
61 let changed_range = {
62 let children = impl_item_list.syntax().children();
63 let last_whitespace = children.filter_map(ast::Whitespace::cast).last();
64
65 last_whitespace.map(|w| w.syntax().range()).unwrap_or_else(|| {
66 let in_brackets = impl_item_list.syntax().range().end() - TextUnit::of_str("}");
67 TextRange::from_to(in_brackets, in_brackets)
68 })
69 };
70
71 let func_bodies = format!("\n{}", missing_fns.into_iter().map(build_func_body).join("\n"));
72 let trailing_whitespace = format!("\n{}", parent_indent);
73 let func_bodies = reindent(&func_bodies, &indent) + &trailing_whitespace;
74
75 let replaced_text_range = TextUnit::of_str(&func_bodies);
76
77 edit.replace(changed_range, func_bodies);
78 edit.set_cursor(
79 changed_range.start() + replaced_text_range - TextUnit::of_str(&trailing_whitespace),
80 );
81 });
82
83 ctx.build()
84}
85
86/// Given an `ast::ImplBlock`, resolves the target trait (the one being
87/// implemented) to a `ast::TraitDef`.
88fn resolve_target_trait_def(
89 db: &impl HirDatabase,
90 resolver: &Resolver,
91 impl_block: &ast::ImplBlock,
92) -> Option<TreeArc<ast::TraitDef>> {
93 let ast_path = impl_block.target_trait().map(AstNode::syntax).and_then(ast::PathType::cast)?;
94 let hir_path = ast_path.path().and_then(hir::Path::from_ast)?;
95
96 match resolver.resolve_path(db, &hir_path).take_types() {
97 Some(hir::Resolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).1),
98 _ => None,
99 }
100}
101
102fn build_func_body(def: &ast::FnDef) -> String {
103 let mut buf = String::new();
104
105 for child in def.syntax().children() {
106 if child.kind() == SyntaxKind::SEMI {
107 buf.push_str(" { unimplemented!() }")
108 } else {
109 child.text().push_to(&mut buf);
110 }
111 }
112
113 buf.trim_end().to_string()
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::helpers::{check_assist, check_assist_not_applicable};
120
121 #[test]
122 fn test_add_missing_impl_members() {
123 check_assist(
124 add_missing_impl_members,
125 "
126trait Foo {
127 fn foo(&self);
128 fn bar(&self);
129 fn baz(&self);
130}
131
132struct S;
133
134impl Foo for S {
135 fn bar(&self) {}
136 <|>
137}",
138 "
139trait Foo {
140 fn foo(&self);
141 fn bar(&self);
142 fn baz(&self);
143}
144
145struct S;
146
147impl Foo for S {
148 fn bar(&self) {}
149 fn foo(&self) { unimplemented!() }
150 fn baz(&self) { unimplemented!() }<|>
151}",
152 );
153 }
154
155 #[test]
156 fn test_copied_overriden_members() {
157 check_assist(
158 add_missing_impl_members,
159 "
160trait Foo {
161 fn foo(&self);
162 fn bar(&self) -> bool { true }
163 fn baz(&self) -> u32 { 42 }
164}
165
166struct S;
167
168impl Foo for S {
169 fn bar(&self) {}
170 <|>
171}",
172 "
173trait Foo {
174 fn foo(&self);
175 fn bar(&self) -> bool { true }
176 fn baz(&self) -> u32 { 42 }
177}
178
179struct S;
180
181impl Foo for S {
182 fn bar(&self) {}
183 fn foo(&self) { unimplemented!() }
184 fn baz(&self) -> u32 { 42 }<|>
185}",
186 );
187 }
188
189 #[test]
190 fn test_empty_impl_block() {
191 check_assist(
192 add_missing_impl_members,
193 "
194trait Foo { fn foo(&self); }
195struct S;
196impl Foo for S {<|>}",
197 "
198trait Foo { fn foo(&self); }
199struct S;
200impl Foo for S {
201 fn foo(&self) { unimplemented!() }<|>
202}",
203 );
204 }
205
206 #[test]
207 fn test_cursor_after_empty_impl_block() {
208 check_assist(
209 add_missing_impl_members,
210 "
211trait Foo { fn foo(&self); }
212struct S;
213impl Foo for S {}<|>",
214 "
215trait Foo { fn foo(&self); }
216struct S;
217impl Foo for S {
218 fn foo(&self) { unimplemented!() }<|>
219}",
220 )
221 }
222
223 #[test]
224 fn test_empty_trait() {
225 check_assist_not_applicable(
226 add_missing_impl_members,
227 "
228trait Foo;
229struct S;
230impl Foo for S { <|> }",
231 )
232 }
233
234 #[test]
235 fn test_ignore_unnamed_trait_members() {
236 check_assist(
237 add_missing_impl_members,
238 "
239trait Foo {
240 fn (arg: u32);
241 fn valid(some: u32) -> bool { false }
242}
243struct S;
244impl Foo for S { <|> }",
245 "
246trait Foo {
247 fn (arg: u32);
248 fn valid(some: u32) -> bool { false }
249}
250struct S;
251impl Foo for S {
252 fn valid(some: u32) -> bool { false }<|>
253}",
254 )
255 }
256
257 #[test]
258 fn test_indented_impl_block() {
259 check_assist(
260 add_missing_impl_members,
261 "
262trait Foo {
263 fn valid(some: u32) -> bool { false }
264}
265struct S;
266
267mod my_mod {
268 impl crate::Foo for S { <|> }
269}",
270 "
271trait Foo {
272 fn valid(some: u32) -> bool { false }
273}
274struct S;
275
276mod my_mod {
277 impl crate::Foo for S {
278 fn valid(some: u32) -> bool { false }<|>
279 }
280}",
281 )
282 }
283}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 6c3d75d79..0c4abb450 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -95,6 +95,7 @@ mod replace_if_let_with_match;
95mod split_import; 95mod split_import;
96mod remove_dbg; 96mod remove_dbg;
97mod auto_import; 97mod auto_import;
98mod add_missing_impl_members;
98 99
99fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { 100fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] {
100 &[ 101 &[
@@ -108,6 +109,7 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis
108 split_import::split_import, 109 split_import::split_import,
109 remove_dbg::remove_dbg, 110 remove_dbg::remove_dbg,
110 auto_import::auto_import, 111 auto_import::auto_import,
112 add_missing_impl_members::add_missing_impl_members,
111 ] 113 ]
112} 114}
113 115