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_assists/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_assists/src')
-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 |
3 files changed, 88 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; |