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 | |
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')
-rw-r--r-- | crates/ra_assists/src/handlers/add_missing_impl_members.rs | 47 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/utils.rs | 75 | ||||
-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 |
6 files changed, 536 insertions, 36 deletions
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs index 448697d31..ab21388c8 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use hir::{db::HirDatabase, HasSource, InFile}; | 1 | use hir::{HasSource, InFile}; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | ast::{self, edit, make, AstNode, NameOwner}, | 3 | ast::{self, edit, make, AstNode, NameOwner}, |
4 | SmolStr, | 4 | SmolStr, |
@@ -6,6 +6,7 @@ use ra_syntax::{ | |||
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 8 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
9 | utils::{get_missing_impl_items, resolve_target_trait}, | ||
9 | Assist, AssistCtx, AssistId, | 10 | Assist, AssistCtx, AssistId, |
10 | }; | 11 | }; |
11 | 12 | ||
@@ -103,11 +104,9 @@ fn add_missing_impl_members_inner( | |||
103 | let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?; | 104 | let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?; |
104 | let impl_item_list = impl_node.item_list()?; | 105 | let impl_item_list = impl_node.item_list()?; |
105 | 106 | ||
106 | let (trait_, trait_def) = { | 107 | let analyzer = ctx.source_analyzer(impl_node.syntax(), None); |
107 | let analyzer = ctx.source_analyzer(impl_node.syntax(), None); | ||
108 | 108 | ||
109 | resolve_target_trait_def(ctx.db, &analyzer, &impl_node)? | 109 | let trait_ = resolve_target_trait(ctx.db, &analyzer, &impl_node)?; |
110 | }; | ||
111 | 110 | ||
112 | let def_name = |item: &ast::ImplItem| -> Option<SmolStr> { | 111 | let def_name = |item: &ast::ImplItem| -> Option<SmolStr> { |
113 | match item { | 112 | match item { |
@@ -118,11 +117,14 @@ fn add_missing_impl_members_inner( | |||
118 | .map(|it| it.text().clone()) | 117 | .map(|it| it.text().clone()) |
119 | }; | 118 | }; |
120 | 119 | ||
121 | let trait_items = trait_def.item_list()?.impl_items(); | 120 | let missing_items = get_missing_impl_items(ctx.db, &analyzer, &impl_node) |
122 | let impl_items = impl_item_list.impl_items().collect::<Vec<_>>(); | 121 | .iter() |
123 | 122 | .map(|i| match i { | |
124 | let missing_items: Vec<_> = trait_items | 123 | hir::AssocItem::Function(i) => ast::ImplItem::FnDef(i.source(ctx.db).value), |
125 | .filter(|t| def_name(t).is_some()) | 124 | hir::AssocItem::TypeAlias(i) => ast::ImplItem::TypeAliasDef(i.source(ctx.db).value), |
125 | hir::AssocItem::Const(i) => ast::ImplItem::ConstDef(i.source(ctx.db).value), | ||
126 | }) | ||
127 | .filter(|t| def_name(&t).is_some()) | ||
126 | .filter(|t| match t { | 128 | .filter(|t| match t { |
127 | ast::ImplItem::FnDef(def) => match mode { | 129 | ast::ImplItem::FnDef(def) => match mode { |
128 | AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), | 130 | AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), |
@@ -130,8 +132,8 @@ fn add_missing_impl_members_inner( | |||
130 | }, | 132 | }, |
131 | _ => mode == AddMissingImplMembersMode::NoDefaultMethods, | 133 | _ => mode == AddMissingImplMembersMode::NoDefaultMethods, |
132 | }) | 134 | }) |
133 | .filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t))) | 135 | .collect::<Vec<_>>(); |
134 | .collect(); | 136 | |
135 | if missing_items.is_empty() { | 137 | if missing_items.is_empty() { |
136 | return None; | 138 | return None; |
137 | } | 139 | } |
@@ -177,27 +179,6 @@ fn add_body(fn_def: ast::FnDef) -> ast::FnDef { | |||
177 | } | 179 | } |
178 | } | 180 | } |
179 | 181 | ||
180 | /// Given an `ast::ImplBlock`, resolves the target trait (the one being | ||
181 | /// implemented) to a `ast::TraitDef`. | ||
182 | fn resolve_target_trait_def( | ||
183 | db: &impl HirDatabase, | ||
184 | analyzer: &hir::SourceAnalyzer, | ||
185 | impl_block: &ast::ImplBlock, | ||
186 | ) -> Option<(hir::Trait, ast::TraitDef)> { | ||
187 | let ast_path = impl_block | ||
188 | .target_trait() | ||
189 | .map(|it| it.syntax().clone()) | ||
190 | .and_then(ast::PathType::cast)? | ||
191 | .path()?; | ||
192 | |||
193 | match analyzer.resolve_path(db, &ast_path) { | ||
194 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => { | ||
195 | Some((def, def.source(db).value)) | ||
196 | } | ||
197 | _ => None, | ||
198 | } | ||
199 | } | ||
200 | |||
201 | #[cfg(test)] | 182 | #[cfg(test)] |
202 | mod tests { | 183 | mod tests { |
203 | use super::*; | 184 | use super::*; |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 828a8e9e8..cb124eaf0 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -9,7 +9,7 @@ mod assist_ctx; | |||
9 | mod marks; | 9 | mod marks; |
10 | #[cfg(test)] | 10 | #[cfg(test)] |
11 | mod doc_tests; | 11 | mod doc_tests; |
12 | mod utils; | 12 | pub mod utils; |
13 | pub mod ast_transform; | 13 | pub mod ast_transform; |
14 | 14 | ||
15 | use ra_db::FileRange; | 15 | use ra_db::FileRange; |
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index 0d5722295..6ff44c95c 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs | |||
@@ -1,10 +1,81 @@ | |||
1 | //! Assorted functions shared by several assists. | 1 | //! Assorted functions shared by several assists. |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | ast::{self, make}, | 4 | ast::{self, make, NameOwner}, |
5 | T, | 5 | AstNode, T, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use hir::db::HirDatabase; | ||
9 | use rustc_hash::FxHashSet; | ||
10 | |||
11 | pub fn get_missing_impl_items( | ||
12 | db: &impl HirDatabase, | ||
13 | analyzer: &hir::SourceAnalyzer, | ||
14 | impl_block: &ast::ImplBlock, | ||
15 | ) -> Vec<hir::AssocItem> { | ||
16 | // Names must be unique between constants and functions. However, type aliases | ||
17 | // may share the same name as a function or constant. | ||
18 | let mut impl_fns_consts = FxHashSet::default(); | ||
19 | let mut impl_type = FxHashSet::default(); | ||
20 | |||
21 | if let Some(item_list) = impl_block.item_list() { | ||
22 | for item in item_list.impl_items() { | ||
23 | match item { | ||
24 | ast::ImplItem::FnDef(f) => { | ||
25 | if let Some(n) = f.name() { | ||
26 | impl_fns_consts.insert(n.syntax().to_string()); | ||
27 | } | ||
28 | } | ||
29 | |||
30 | ast::ImplItem::TypeAliasDef(t) => { | ||
31 | if let Some(n) = t.name() { | ||
32 | impl_type.insert(n.syntax().to_string()); | ||
33 | } | ||
34 | } | ||
35 | |||
36 | ast::ImplItem::ConstDef(c) => { | ||
37 | if let Some(n) = c.name() { | ||
38 | impl_fns_consts.insert(n.syntax().to_string()); | ||
39 | } | ||
40 | } | ||
41 | } | ||
42 | } | ||
43 | } | ||
44 | |||
45 | resolve_target_trait(db, analyzer, impl_block).map_or(vec![], |target_trait| { | ||
46 | target_trait | ||
47 | .items(db) | ||
48 | .iter() | ||
49 | .filter(|i| match i { | ||
50 | hir::AssocItem::Function(f) => !impl_fns_consts.contains(&f.name(db).to_string()), | ||
51 | hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(db).to_string()), | ||
52 | hir::AssocItem::Const(c) => c | ||
53 | .name(db) | ||
54 | .map(|n| !impl_fns_consts.contains(&n.to_string())) | ||
55 | .unwrap_or_default(), | ||
56 | }) | ||
57 | .cloned() | ||
58 | .collect() | ||
59 | }) | ||
60 | } | ||
61 | |||
62 | pub(crate) fn resolve_target_trait( | ||
63 | db: &impl HirDatabase, | ||
64 | analyzer: &hir::SourceAnalyzer, | ||
65 | impl_block: &ast::ImplBlock, | ||
66 | ) -> Option<hir::Trait> { | ||
67 | let ast_path = impl_block | ||
68 | .target_trait() | ||
69 | .map(|it| it.syntax().clone()) | ||
70 | .and_then(ast::PathType::cast)? | ||
71 | .path()?; | ||
72 | |||
73 | match analyzer.resolve_path(db, &ast_path) { | ||
74 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def), | ||
75 | _ => None, | ||
76 | } | ||
77 | } | ||
78 | |||
8 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { | 79 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { |
9 | if let Some(expr) = invert_special_case(&expr) { | 80 | if let Some(expr) = invert_special_case(&expr) { |
10 | return expr; | 81 | return expr; |
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() |