diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-02-17 09:34:08 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-02-17 09:34:08 +0000 |
commit | 8d8d542dfa1d3b83088a1f48a91665e95fd008cc (patch) | |
tree | 495e3d223f595404e46d5e58b426cda4b0c633bc /crates/ra_ide/src | |
parent | 953dbe3e02fa87bab2f12452746e6c50a47ac153 (diff) | |
parent | 057d0bee5516dc7cba71479b27227c5ad22140ee (diff) |
Merge #3108
3108: Magic Completion for `impl Trait for` Associated Items r=matklad a=kdelorey
# Summary
This PR adds a set of magic completions to auto complete associated trait items (functions/consts/types).
![Associated Trait Impl](https://user-images.githubusercontent.com/2295721/74493144-d8f1af00-4e96-11ea-93a4-82725bf89646.gif)
## Notes
Since the assist and completion share the same logic when figuring out the associated items that are missing, a shared utility was created in the `ra_assists::utils` module.
Resolves #1046
As this is my first PR to the rust-analyzer project, I'm new to the codebase, feedback welcomed!
Co-authored-by: Kevin DeLorey <[email protected]>
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r-- | crates/ra_ide/src/completion.rs | 3 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_trait_impl.rs | 436 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 9 |
3 files changed, 448 insertions, 0 deletions
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index fedc02e14..4bdc6ba23 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -15,6 +15,7 @@ mod complete_path; | |||
15 | mod complete_scope; | 15 | mod complete_scope; |
16 | mod complete_postfix; | 16 | mod complete_postfix; |
17 | mod complete_macro_in_item_position; | 17 | mod complete_macro_in_item_position; |
18 | mod complete_trait_impl; | ||
18 | 19 | ||
19 | use ra_db::SourceDatabase; | 20 | use ra_db::SourceDatabase; |
20 | use ra_ide_db::RootDatabase; | 21 | use ra_ide_db::RootDatabase; |
@@ -74,5 +75,7 @@ pub(crate) fn completions(db: &RootDatabase, position: FilePosition) -> Option<C | |||
74 | complete_pattern::complete_pattern(&mut acc, &ctx); | 75 | complete_pattern::complete_pattern(&mut acc, &ctx); |
75 | complete_postfix::complete_postfix(&mut acc, &ctx); | 76 | complete_postfix::complete_postfix(&mut acc, &ctx); |
76 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); | 77 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); |
78 | complete_trait_impl::complete_trait_impl(&mut acc, &ctx); | ||
79 | |||
77 | Some(acc) | 80 | Some(acc) |
78 | } | 81 | } |
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs new file mode 100644 index 000000000..6ff10c017 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs | |||
@@ -0,0 +1,436 @@ | |||
1 | //! Completion for associated items in a trait implementation. | ||
2 | //! | ||
3 | //! This module adds the completion items related to implementing associated | ||
4 | //! items within a `impl Trait for Struct` block. The current context node | ||
5 | //! must be within either a `FN_DEF`, `TYPE_ALIAS_DEF`, or `CONST_DEF` node | ||
6 | //! and an direct child of an `IMPL_BLOCK`. | ||
7 | //! | ||
8 | //! # Examples | ||
9 | //! | ||
10 | //! Considering the following trait `impl`: | ||
11 | //! | ||
12 | //! ```ignore | ||
13 | //! trait SomeTrait { | ||
14 | //! fn foo(); | ||
15 | //! } | ||
16 | //! | ||
17 | //! impl SomeTrait for () { | ||
18 | //! fn f<|> | ||
19 | //! } | ||
20 | //! ``` | ||
21 | //! | ||
22 | //! may result in the completion of the following method: | ||
23 | //! | ||
24 | //! ```ignore | ||
25 | //! # trait SomeTrait { | ||
26 | //! # fn foo(); | ||
27 | //! # } | ||
28 | //! | ||
29 | //! impl SomeTrait for () { | ||
30 | //! fn foo() {}<|> | ||
31 | //! } | ||
32 | //! ``` | ||
33 | |||
34 | use hir::{self, Docs, HasSource}; | ||
35 | use ra_assists::utils::get_missing_impl_items; | ||
36 | use ra_syntax::{ | ||
37 | ast::{self, edit}, | ||
38 | AstNode, SyntaxKind, SyntaxNode, TextRange, | ||
39 | }; | ||
40 | use ra_text_edit::TextEdit; | ||
41 | |||
42 | use crate::{ | ||
43 | completion::{ | ||
44 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | ||
45 | }, | ||
46 | display::FunctionSignature, | ||
47 | }; | ||
48 | |||
49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | ||
50 | let trigger = ctx.token.ancestors().find(|p| match p.kind() { | ||
51 | SyntaxKind::FN_DEF | ||
52 | | SyntaxKind::TYPE_ALIAS_DEF | ||
53 | | SyntaxKind::CONST_DEF | ||
54 | | SyntaxKind::BLOCK_EXPR => true, | ||
55 | _ => false, | ||
56 | }); | ||
57 | |||
58 | let impl_block = trigger | ||
59 | .as_ref() | ||
60 | .and_then(|node| node.parent()) | ||
61 | .and_then(|node| node.parent()) | ||
62 | .and_then(|node| ast::ImplBlock::cast(node)); | ||
63 | |||
64 | if let (Some(trigger), Some(impl_block)) = (trigger, impl_block) { | ||
65 | match trigger.kind() { | ||
66 | SyntaxKind::FN_DEF => { | ||
67 | for missing_fn in get_missing_impl_items(ctx.db, &ctx.analyzer, &impl_block) | ||
68 | .iter() | ||
69 | .filter_map(|item| match item { | ||
70 | hir::AssocItem::Function(fn_item) => Some(fn_item), | ||
71 | _ => None, | ||
72 | }) | ||
73 | { | ||
74 | add_function_impl(&trigger, acc, ctx, &missing_fn); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | SyntaxKind::TYPE_ALIAS_DEF => { | ||
79 | for missing_fn in get_missing_impl_items(ctx.db, &ctx.analyzer, &impl_block) | ||
80 | .iter() | ||
81 | .filter_map(|item| match item { | ||
82 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | ||
83 | _ => None, | ||
84 | }) | ||
85 | { | ||
86 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); | ||
87 | } | ||
88 | } | ||
89 | |||
90 | SyntaxKind::CONST_DEF => { | ||
91 | for missing_fn in get_missing_impl_items(ctx.db, &ctx.analyzer, &impl_block) | ||
92 | .iter() | ||
93 | .filter_map(|item| match item { | ||
94 | hir::AssocItem::Const(const_item) => Some(const_item), | ||
95 | _ => None, | ||
96 | }) | ||
97 | { | ||
98 | add_const_impl(&trigger, acc, ctx, &missing_fn); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | _ => {} | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | |||
107 | fn add_function_impl( | ||
108 | fn_def_node: &SyntaxNode, | ||
109 | acc: &mut Completions, | ||
110 | ctx: &CompletionContext, | ||
111 | func: &hir::Function, | ||
112 | ) { | ||
113 | let display = FunctionSignature::from_hir(ctx.db, func.clone()); | ||
114 | |||
115 | let fn_name = func.name(ctx.db).to_string(); | ||
116 | |||
117 | let label = if func.params(ctx.db).len() > 0 { | ||
118 | format!("fn {}(..)", fn_name) | ||
119 | } else { | ||
120 | format!("fn {}()", fn_name) | ||
121 | }; | ||
122 | |||
123 | let builder = CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label.clone()) | ||
124 | .lookup_by(fn_name) | ||
125 | .set_documentation(func.docs(ctx.db)); | ||
126 | |||
127 | let completion_kind = if func.has_self_param(ctx.db) { | ||
128 | CompletionItemKind::Method | ||
129 | } else { | ||
130 | CompletionItemKind::Function | ||
131 | }; | ||
132 | |||
133 | let snippet = format!("{} {{}}", display); | ||
134 | |||
135 | let range = TextRange::from_to(fn_def_node.text_range().start(), ctx.source_range().end()); | ||
136 | |||
137 | builder.text_edit(TextEdit::replace(range, snippet)).kind(completion_kind).add_to(acc); | ||
138 | } | ||
139 | |||
140 | fn add_type_alias_impl( | ||
141 | type_def_node: &SyntaxNode, | ||
142 | acc: &mut Completions, | ||
143 | ctx: &CompletionContext, | ||
144 | type_alias: &hir::TypeAlias, | ||
145 | ) { | ||
146 | let alias_name = type_alias.name(ctx.db).to_string(); | ||
147 | |||
148 | let snippet = format!("type {} = ", alias_name); | ||
149 | |||
150 | let range = TextRange::from_to(type_def_node.text_range().start(), ctx.source_range().end()); | ||
151 | |||
152 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) | ||
153 | .text_edit(TextEdit::replace(range, snippet)) | ||
154 | .lookup_by(alias_name) | ||
155 | .kind(CompletionItemKind::TypeAlias) | ||
156 | .set_documentation(type_alias.docs(ctx.db)) | ||
157 | .add_to(acc); | ||
158 | } | ||
159 | |||
160 | fn add_const_impl( | ||
161 | const_def_node: &SyntaxNode, | ||
162 | acc: &mut Completions, | ||
163 | ctx: &CompletionContext, | ||
164 | const_: &hir::Const, | ||
165 | ) { | ||
166 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); | ||
167 | |||
168 | if let Some(const_name) = const_name { | ||
169 | let snippet = make_const_compl_syntax(&const_.source(ctx.db).value); | ||
170 | |||
171 | let range = | ||
172 | TextRange::from_to(const_def_node.text_range().start(), ctx.source_range().end()); | ||
173 | |||
174 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) | ||
175 | .text_edit(TextEdit::replace(range, snippet)) | ||
176 | .lookup_by(const_name) | ||
177 | .kind(CompletionItemKind::Const) | ||
178 | .set_documentation(const_.docs(ctx.db)) | ||
179 | .add_to(acc); | ||
180 | } | ||
181 | } | ||
182 | |||
183 | fn make_const_compl_syntax(const_: &ast::ConstDef) -> String { | ||
184 | let const_ = edit::strip_attrs_and_docs(const_); | ||
185 | |||
186 | let const_start = const_.syntax().text_range().start(); | ||
187 | let const_end = const_.syntax().text_range().end(); | ||
188 | |||
189 | let start = | ||
190 | const_.syntax().first_child_or_token().map_or(const_start, |f| f.text_range().start()); | ||
191 | |||
192 | let end = const_ | ||
193 | .syntax() | ||
194 | .children_with_tokens() | ||
195 | .find(|s| s.kind() == SyntaxKind::SEMI || s.kind() == SyntaxKind::EQ) | ||
196 | .map_or(const_end, |f| f.text_range().start()); | ||
197 | |||
198 | let len = end - start; | ||
199 | let range = TextRange::from_to(0.into(), len); | ||
200 | |||
201 | let syntax = const_.syntax().text().slice(range).to_string(); | ||
202 | |||
203 | format!("{} = ", syntax.trim_end()) | ||
204 | } | ||
205 | |||
206 | #[cfg(test)] | ||
207 | mod tests { | ||
208 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | ||
209 | use insta::assert_debug_snapshot; | ||
210 | |||
211 | fn complete(code: &str) -> Vec<CompletionItem> { | ||
212 | do_completion(code, CompletionKind::Magic) | ||
213 | } | ||
214 | |||
215 | #[test] | ||
216 | fn single_function() { | ||
217 | let completions = complete( | ||
218 | r" | ||
219 | trait Test { | ||
220 | fn foo(); | ||
221 | } | ||
222 | |||
223 | struct T1; | ||
224 | |||
225 | impl Test for T1 { | ||
226 | fn f<|> | ||
227 | } | ||
228 | ", | ||
229 | ); | ||
230 | assert_debug_snapshot!(completions, @r###" | ||
231 | [ | ||
232 | CompletionItem { | ||
233 | label: "fn foo()", | ||
234 | source_range: [141; 142), | ||
235 | delete: [138; 142), | ||
236 | insert: "fn foo() {}", | ||
237 | kind: Function, | ||
238 | lookup: "foo", | ||
239 | }, | ||
240 | ] | ||
241 | "###); | ||
242 | } | ||
243 | |||
244 | #[test] | ||
245 | fn hide_implemented_fn() { | ||
246 | let completions = complete( | ||
247 | r" | ||
248 | trait Test { | ||
249 | fn foo(); | ||
250 | fn foo_bar(); | ||
251 | } | ||
252 | |||
253 | struct T1; | ||
254 | |||
255 | impl Test for T1 { | ||
256 | fn foo() {} | ||
257 | |||
258 | fn f<|> | ||
259 | } | ||
260 | ", | ||
261 | ); | ||
262 | assert_debug_snapshot!(completions, @r###" | ||
263 | [ | ||
264 | CompletionItem { | ||
265 | label: "fn foo_bar()", | ||
266 | source_range: [200; 201), | ||
267 | delete: [197; 201), | ||
268 | insert: "fn foo_bar() {}", | ||
269 | kind: Function, | ||
270 | lookup: "foo_bar", | ||
271 | }, | ||
272 | ] | ||
273 | "###); | ||
274 | } | ||
275 | |||
276 | #[test] | ||
277 | fn completes_only_on_top_level() { | ||
278 | let completions = complete( | ||
279 | r" | ||
280 | trait Test { | ||
281 | fn foo(); | ||
282 | |||
283 | fn foo_bar(); | ||
284 | } | ||
285 | |||
286 | struct T1; | ||
287 | |||
288 | impl Test for T1 { | ||
289 | fn foo() { | ||
290 | <|> | ||
291 | } | ||
292 | } | ||
293 | ", | ||
294 | ); | ||
295 | assert_debug_snapshot!(completions, @r###"[]"###); | ||
296 | } | ||
297 | |||
298 | #[test] | ||
299 | fn generic_fn() { | ||
300 | let completions = complete( | ||
301 | r" | ||
302 | trait Test { | ||
303 | fn foo<T>(); | ||
304 | } | ||
305 | |||
306 | struct T1; | ||
307 | |||
308 | impl Test for T1 { | ||
309 | fn f<|> | ||
310 | } | ||
311 | ", | ||
312 | ); | ||
313 | assert_debug_snapshot!(completions, @r###" | ||
314 | [ | ||
315 | CompletionItem { | ||
316 | label: "fn foo()", | ||
317 | source_range: [144; 145), | ||
318 | delete: [141; 145), | ||
319 | insert: "fn foo<T>() {}", | ||
320 | kind: Function, | ||
321 | lookup: "foo", | ||
322 | }, | ||
323 | ] | ||
324 | "###); | ||
325 | } | ||
326 | |||
327 | #[test] | ||
328 | fn generic_constrait_fn() { | ||
329 | let completions = complete( | ||
330 | r" | ||
331 | trait Test { | ||
332 | fn foo<T>() where T: Into<String>; | ||
333 | } | ||
334 | |||
335 | struct T1; | ||
336 | |||
337 | impl Test for T1 { | ||
338 | fn f<|> | ||
339 | } | ||
340 | ", | ||
341 | ); | ||
342 | assert_debug_snapshot!(completions, @r###" | ||
343 | [ | ||
344 | CompletionItem { | ||
345 | label: "fn foo()", | ||
346 | source_range: [166; 167), | ||
347 | delete: [163; 167), | ||
348 | insert: "fn foo<T>()\nwhere T: Into<String> {}", | ||
349 | kind: Function, | ||
350 | lookup: "foo", | ||
351 | }, | ||
352 | ] | ||
353 | "###); | ||
354 | } | ||
355 | |||
356 | #[test] | ||
357 | fn associated_type() { | ||
358 | let completions = complete( | ||
359 | r" | ||
360 | trait Test { | ||
361 | type SomeType; | ||
362 | } | ||
363 | |||
364 | impl Test for () { | ||
365 | type S<|> | ||
366 | } | ||
367 | ", | ||
368 | ); | ||
369 | assert_debug_snapshot!(completions, @r###" | ||
370 | [ | ||
371 | CompletionItem { | ||
372 | label: "type SomeType = ", | ||
373 | source_range: [124; 125), | ||
374 | delete: [119; 125), | ||
375 | insert: "type SomeType = ", | ||
376 | kind: TypeAlias, | ||
377 | lookup: "SomeType", | ||
378 | }, | ||
379 | ] | ||
380 | "###); | ||
381 | } | ||
382 | |||
383 | #[test] | ||
384 | fn associated_const() { | ||
385 | let completions = complete( | ||
386 | r" | ||
387 | trait Test { | ||
388 | const SOME_CONST: u16; | ||
389 | } | ||
390 | |||
391 | impl Test for () { | ||
392 | const S<|> | ||
393 | } | ||
394 | ", | ||
395 | ); | ||
396 | assert_debug_snapshot!(completions, @r###" | ||
397 | [ | ||
398 | CompletionItem { | ||
399 | label: "const SOME_CONST: u16 = ", | ||
400 | source_range: [133; 134), | ||
401 | delete: [127; 134), | ||
402 | insert: "const SOME_CONST: u16 = ", | ||
403 | kind: Const, | ||
404 | lookup: "SOME_CONST", | ||
405 | }, | ||
406 | ] | ||
407 | "###); | ||
408 | } | ||
409 | |||
410 | #[test] | ||
411 | fn associated_const_with_default() { | ||
412 | let completions = complete( | ||
413 | r" | ||
414 | trait Test { | ||
415 | const SOME_CONST: u16 = 42; | ||
416 | } | ||
417 | |||
418 | impl Test for () { | ||
419 | const S<|> | ||
420 | } | ||
421 | ", | ||
422 | ); | ||
423 | assert_debug_snapshot!(completions, @r###" | ||
424 | [ | ||
425 | CompletionItem { | ||
426 | label: "const SOME_CONST: u16 = ", | ||
427 | source_range: [138; 139), | ||
428 | delete: [132; 139), | ||
429 | insert: "const SOME_CONST: u16 = ", | ||
430 | kind: Const, | ||
431 | lookup: "SOME_CONST", | ||
432 | }, | ||
433 | ] | ||
434 | "###); | ||
435 | } | ||
436 | } | ||
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 5a0407fd7..8678a3234 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -25,6 +25,7 @@ pub(crate) struct CompletionContext<'a> { | |||
25 | pub(super) use_item_syntax: Option<ast::UseItem>, | 25 | pub(super) use_item_syntax: Option<ast::UseItem>, |
26 | pub(super) record_lit_syntax: Option<ast::RecordLit>, | 26 | pub(super) record_lit_syntax: Option<ast::RecordLit>, |
27 | pub(super) record_lit_pat: Option<ast::RecordPat>, | 27 | pub(super) record_lit_pat: Option<ast::RecordPat>, |
28 | pub(super) impl_block: Option<ast::ImplBlock>, | ||
28 | pub(super) is_param: bool, | 29 | pub(super) is_param: bool, |
29 | /// If a name-binding or reference to a const in a pattern. | 30 | /// If a name-binding or reference to a const in a pattern. |
30 | /// Irrefutable patterns (like let) are excluded. | 31 | /// Irrefutable patterns (like let) are excluded. |
@@ -72,6 +73,7 @@ impl<'a> CompletionContext<'a> { | |||
72 | use_item_syntax: None, | 73 | use_item_syntax: None, |
73 | record_lit_syntax: None, | 74 | record_lit_syntax: None, |
74 | record_lit_pat: None, | 75 | record_lit_pat: None, |
76 | impl_block: None, | ||
75 | is_param: false, | 77 | is_param: false, |
76 | is_pat_binding: false, | 78 | is_pat_binding: false, |
77 | is_trivial_path: false, | 79 | is_trivial_path: false, |
@@ -148,6 +150,13 @@ impl<'a> CompletionContext<'a> { | |||
148 | self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset); | 150 | self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset); |
149 | } | 151 | } |
150 | 152 | ||
153 | self.impl_block = self | ||
154 | .token | ||
155 | .parent() | ||
156 | .ancestors() | ||
157 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
158 | .find_map(ast::ImplBlock::cast); | ||
159 | |||
151 | let top_node = name_ref | 160 | let top_node = name_ref |
152 | .syntax() | 161 | .syntax() |
153 | .ancestors() | 162 | .ancestors() |