diff options
-rw-r--r-- | crates/ra_ide/src/completion.rs | 3 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_trait_impl.rs | 293 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 9 |
3 files changed, 305 insertions, 0 deletions
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index fedc02e14..4f24cd1f9 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..0175f5e55 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs | |||
@@ -0,0 +1,293 @@ | |||
1 | use crate::completion::{CompletionContext, Completions, CompletionItem, CompletionKind, CompletionItemKind}; | ||
2 | |||
3 | use ra_syntax::ast::{self, NameOwner, AstNode}; | ||
4 | |||
5 | use hir::{self, db::HirDatabase, Docs}; | ||
6 | |||
7 | |||
8 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | ||
9 | let impl_block = ctx.impl_block.as_ref(); | ||
10 | let item_list = impl_block.and_then(|i| i.item_list()); | ||
11 | |||
12 | if item_list.is_none() | ||
13 | || impl_block.is_none() | ||
14 | || ctx.function_syntax.is_some() { | ||
15 | return; | ||
16 | } | ||
17 | |||
18 | let item_list = item_list.unwrap(); | ||
19 | let impl_block = impl_block.unwrap(); | ||
20 | |||
21 | let target_trait = resolve_target_trait(ctx.db, &ctx.analyzer, &impl_block); | ||
22 | if target_trait.is_none() { | ||
23 | return; | ||
24 | } | ||
25 | |||
26 | let target_trait = target_trait.unwrap(); | ||
27 | |||
28 | let trait_items = target_trait.items(ctx.db); | ||
29 | let missing_items = trait_items | ||
30 | .iter() | ||
31 | .filter(|i| { | ||
32 | match i { | ||
33 | hir::AssocItem::Function(f) => { | ||
34 | let f_name = f.name(ctx.db).to_string(); | ||
35 | |||
36 | item_list | ||
37 | .impl_items() | ||
38 | .find(|impl_item| { | ||
39 | match impl_item { | ||
40 | ast::ImplItem::FnDef(impl_f) => { | ||
41 | if let Some(n) = impl_f.name() { | ||
42 | f_name == n.syntax().to_string() | ||
43 | } else { | ||
44 | false | ||
45 | } | ||
46 | }, | ||
47 | _ => false | ||
48 | } | ||
49 | }).is_none() | ||
50 | }, | ||
51 | hir::AssocItem::Const(c) => { | ||
52 | let c_name = c.name(ctx.db) | ||
53 | .map(|f| f.to_string()); | ||
54 | |||
55 | if c_name.is_none() { | ||
56 | return false; | ||
57 | } | ||
58 | |||
59 | let c_name = c_name.unwrap(); | ||
60 | |||
61 | item_list | ||
62 | .impl_items() | ||
63 | .find(|impl_item| { | ||
64 | match impl_item { | ||
65 | ast::ImplItem::ConstDef(c) => { | ||
66 | if let Some(n) = c.name() { | ||
67 | c_name == n.syntax().to_string() | ||
68 | } else { | ||
69 | false | ||
70 | } | ||
71 | }, | ||
72 | _ => false | ||
73 | } | ||
74 | }).is_none() | ||
75 | }, | ||
76 | hir::AssocItem::TypeAlias(t) => { | ||
77 | let t_name = t.name(ctx.db).to_string(); | ||
78 | |||
79 | item_list | ||
80 | .impl_items() | ||
81 | .find(|impl_item| { | ||
82 | match impl_item { | ||
83 | ast::ImplItem::TypeAliasDef(t) => { | ||
84 | if let Some(n) = t.name() { | ||
85 | t_name == n.syntax().to_string() | ||
86 | } else { | ||
87 | false | ||
88 | } | ||
89 | }, | ||
90 | _ => false | ||
91 | } | ||
92 | }).is_none() | ||
93 | } | ||
94 | } | ||
95 | }); | ||
96 | |||
97 | for item in missing_items { | ||
98 | match item { | ||
99 | hir::AssocItem::Function(f) => add_function_impl(acc, ctx, f), | ||
100 | hir::AssocItem::TypeAlias(t) => add_type_alias_impl(acc, ctx, t), | ||
101 | _ => {}, | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | fn resolve_target_trait( | ||
107 | db: &impl HirDatabase, | ||
108 | analyzer: &hir::SourceAnalyzer, | ||
109 | impl_block: &ast::ImplBlock | ||
110 | ) -> Option<hir::Trait> { | ||
111 | let ast_path = impl_block | ||
112 | .target_trait() | ||
113 | .map(|it| it.syntax().clone()) | ||
114 | .and_then(ast::PathType::cast)? | ||
115 | .path()?; | ||
116 | |||
117 | match analyzer.resolve_path(db, &ast_path) { | ||
118 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => { | ||
119 | Some(def) | ||
120 | } | ||
121 | _ => None, | ||
122 | } | ||
123 | } | ||
124 | |||
125 | fn add_function_impl(acc: &mut Completions, ctx: &CompletionContext, func: &hir::Function) { | ||
126 | use crate::display::FunctionSignature; | ||
127 | |||
128 | let display = FunctionSignature::from_hir(ctx.db, func.clone()); | ||
129 | |||
130 | let func_name = func.name(ctx.db); | ||
131 | |||
132 | let label = if func.params(ctx.db).len() > 0 { | ||
133 | format!("fn {}(..)", func_name.to_string()) | ||
134 | } else { | ||
135 | format!("fn {}()", func_name.to_string()) | ||
136 | }; | ||
137 | |||
138 | let builder = CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label.clone()) | ||
139 | .lookup_by(label) | ||
140 | .set_documentation(func.docs(ctx.db)); | ||
141 | |||
142 | let completion_kind = if func.has_self_param(ctx.db) { | ||
143 | CompletionItemKind::Method | ||
144 | } else { | ||
145 | CompletionItemKind::Function | ||
146 | }; | ||
147 | |||
148 | let snippet = { | ||
149 | let mut s = format!("{}", display); | ||
150 | s.push_str(" {}"); | ||
151 | s | ||
152 | }; | ||
153 | |||
154 | builder | ||
155 | .insert_text(snippet) | ||
156 | .kind(completion_kind) | ||
157 | .add_to(acc); | ||
158 | } | ||
159 | |||
160 | fn add_type_alias_impl(acc: &mut Completions, ctx: &CompletionContext, type_alias: &hir::TypeAlias) { | ||
161 | let snippet = format!("type {} = ", type_alias.name(ctx.db).to_string()); | ||
162 | |||
163 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) | ||
164 | .insert_text(snippet) | ||
165 | .kind(CompletionItemKind::TypeAlias) | ||
166 | .set_documentation(type_alias.docs(ctx.db)) | ||
167 | .add_to(acc); | ||
168 | } | ||
169 | |||
170 | #[cfg(test)] | ||
171 | mod tests { | ||
172 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | ||
173 | use insta::assert_debug_snapshot; | ||
174 | |||
175 | fn complete(code: &str) -> Vec<CompletionItem> { | ||
176 | do_completion(code, CompletionKind::Magic) | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn single_function() { | ||
181 | let completions = complete( | ||
182 | r" | ||
183 | trait Test { | ||
184 | fn foo(); | ||
185 | } | ||
186 | |||
187 | struct T1; | ||
188 | |||
189 | impl Test for T1 { | ||
190 | <|> | ||
191 | } | ||
192 | ", | ||
193 | ); | ||
194 | assert_debug_snapshot!(completions, @r###" | ||
195 | [ | ||
196 | CompletionItem { | ||
197 | label: "fn foo()", | ||
198 | source_range: [138; 138), | ||
199 | delete: [138; 138), | ||
200 | insert: "fn foo() {}", | ||
201 | kind: Function, | ||
202 | }, | ||
203 | ] | ||
204 | "###); | ||
205 | } | ||
206 | |||
207 | #[test] | ||
208 | fn hide_implemented_fn() { | ||
209 | let completions = complete( | ||
210 | r" | ||
211 | trait Test { | ||
212 | fn foo(); | ||
213 | fn bar(); | ||
214 | } | ||
215 | |||
216 | struct T1; | ||
217 | |||
218 | impl Test for T1 { | ||
219 | fn foo() {} | ||
220 | |||
221 | <|> | ||
222 | } | ||
223 | ", | ||
224 | ); | ||
225 | assert_debug_snapshot!(completions, @r###" | ||
226 | [ | ||
227 | CompletionItem { | ||
228 | label: "fn bar()", | ||
229 | source_range: [193; 193), | ||
230 | delete: [193; 193), | ||
231 | insert: "fn bar() {}", | ||
232 | kind: Function, | ||
233 | }, | ||
234 | ] | ||
235 | "###); | ||
236 | } | ||
237 | |||
238 | #[test] | ||
239 | fn generic_fn() { | ||
240 | let completions = complete( | ||
241 | r" | ||
242 | trait Test { | ||
243 | fn foo<T>(); | ||
244 | } | ||
245 | |||
246 | struct T1; | ||
247 | |||
248 | impl Test for T1 { | ||
249 | <|> | ||
250 | } | ||
251 | ", | ||
252 | ); | ||
253 | assert_debug_snapshot!(completions, @r###" | ||
254 | [ | ||
255 | CompletionItem { | ||
256 | label: "fn foo()", | ||
257 | source_range: [141; 141), | ||
258 | delete: [141; 141), | ||
259 | insert: "fn foo<T>() {}", | ||
260 | kind: Function, | ||
261 | }, | ||
262 | ] | ||
263 | "###); | ||
264 | } | ||
265 | |||
266 | #[test] | ||
267 | fn generic_constrait_fn() { | ||
268 | let completions = complete( | ||
269 | r" | ||
270 | trait Test { | ||
271 | fn foo<T>() where T: Into<String>; | ||
272 | } | ||
273 | |||
274 | struct T1; | ||
275 | |||
276 | impl Test for T1 { | ||
277 | <|> | ||
278 | } | ||
279 | ", | ||
280 | ); | ||
281 | assert_debug_snapshot!(completions, @r###" | ||
282 | [ | ||
283 | CompletionItem { | ||
284 | label: "fn foo()", | ||
285 | source_range: [163; 163), | ||
286 | delete: [163; 163), | ||
287 | insert: "fn foo<T>()\nwhere T: Into<String> {}", | ||
288 | kind: Function, | ||
289 | }, | ||
290 | ] | ||
291 | "###); | ||
292 | } | ||
293 | } \ No newline at end of file | ||
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() |