diff options
author | Kirill Bulatov <[email protected]> | 2020-02-11 16:24:43 +0000 |
---|---|---|
committer | Kirill Bulatov <[email protected]> | 2020-02-12 15:18:42 +0000 |
commit | d5c3808545e26d246d75e0754e81de803f9e53e6 (patch) | |
tree | caf2376b06ace612efb274312825251a2c2ab53c /crates/ra_assists/src | |
parent | 8f959f20ee0fecd644054ffed334c378f9ae20f5 (diff) |
Support trait method call autoimports
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/handlers/auto_import.rs | 306 |
1 files changed, 206 insertions, 100 deletions
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index a9778fab7..9a366414c 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs | |||
@@ -1,15 +1,17 @@ | |||
1 | use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase}; | 1 | use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase}; |
2 | use ra_syntax::ast::{self, AstNode}; | 2 | use ra_syntax::{ |
3 | ast::{self, AstNode}, | ||
4 | SyntaxNode, | ||
5 | }; | ||
3 | 6 | ||
4 | use crate::{ | 7 | use crate::{ |
5 | assist_ctx::{Assist, AssistCtx}, | 8 | assist_ctx::{Assist, AssistCtx}, |
6 | insert_use_statement, AssistId, | 9 | insert_use_statement, AssistId, |
7 | }; | 10 | }; |
8 | use ast::{FnDefOwner, ModuleItem, ModuleItemOwner}; | ||
9 | use hir::{ | 11 | use hir::{ |
10 | db::{DefDatabase, HirDatabase}, | 12 | db::{DefDatabase, HirDatabase}, |
11 | Adt, AssocContainerId, Crate, Function, HasSource, InFile, ModPath, Module, ModuleDef, | 13 | AssocContainerId, AssocItem, Crate, Function, ModPath, Module, ModuleDef, PathResolution, |
12 | PathResolution, SourceAnalyzer, SourceBinder, Trait, | 14 | SourceAnalyzer, Trait, Type, |
13 | }; | 15 | }; |
14 | use rustc_hash::FxHashSet; | 16 | use rustc_hash::FxHashSet; |
15 | use std::collections::BTreeSet; | 17 | use std::collections::BTreeSet; |
@@ -34,36 +36,28 @@ use std::collections::BTreeSet; | |||
34 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | 36 | // # pub mod std { pub mod collections { pub struct HashMap { } } } |
35 | // ``` | 37 | // ``` |
36 | pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | 38 | pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { |
37 | let path_under_caret: ast::Path = ctx.find_node_at_offset()?; | 39 | let auto_import_assets = if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() |
38 | if path_under_caret.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { | 40 | { |
39 | return None; | 41 | AutoImportAssets::for_regular_path(path_under_caret, &ctx)? |
40 | } | 42 | } else { |
41 | 43 | AutoImportAssets::for_method_call(ctx.find_node_at_offset()?, &ctx)? | |
42 | let module = path_under_caret.syntax().ancestors().find_map(ast::Module::cast); | ||
43 | let position = match module.and_then(|it| it.item_list()) { | ||
44 | Some(item_list) => item_list.syntax().clone(), | ||
45 | None => { | ||
46 | let current_file = | ||
47 | path_under_caret.syntax().ancestors().find_map(ast::SourceFile::cast)?; | ||
48 | current_file.syntax().clone() | ||
49 | } | ||
50 | }; | 44 | }; |
51 | let source_analyzer = ctx.source_analyzer(&position, None); | ||
52 | let module_with_name_to_import = source_analyzer.module()?; | ||
53 | 45 | ||
54 | let import_candidate = ImportCandidate::new(&path_under_caret, &source_analyzer, ctx.db)?; | 46 | let proposed_imports = auto_import_assets |
55 | let proposed_imports = import_candidate.search_for_imports(ctx.db, module_with_name_to_import); | 47 | .search_for_imports(ctx.db, auto_import_assets.module_with_name_to_import); |
56 | if proposed_imports.is_empty() { | 48 | if proposed_imports.is_empty() { |
57 | return None; | 49 | return None; |
58 | } | 50 | } |
59 | 51 | ||
60 | let mut group = ctx.add_assist_group(format!("Import {}", import_candidate.get_search_query())); | 52 | let mut group = |
53 | // TODO kb create another method and add something about traits there | ||
54 | ctx.add_assist_group(format!("Import {}", auto_import_assets.get_search_query())); | ||
61 | for import in proposed_imports { | 55 | for import in proposed_imports { |
62 | group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { | 56 | group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { |
63 | edit.target(path_under_caret.syntax().text_range()); | 57 | edit.target(auto_import_assets.syntax_under_caret.text_range()); |
64 | insert_use_statement( | 58 | insert_use_statement( |
65 | &position, | 59 | &auto_import_assets.syntax_under_caret, |
66 | path_under_caret.syntax(), | 60 | &auto_import_assets.syntax_under_caret, |
67 | &import, | 61 | &import, |
68 | edit.text_edit_builder(), | 62 | edit.text_edit_builder(), |
69 | ); | 63 | ); |
@@ -72,64 +66,55 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | |||
72 | group.finish() | 66 | group.finish() |
73 | } | 67 | } |
74 | 68 | ||
75 | #[derive(Debug)] | 69 | struct AutoImportAssets { |
76 | // TODO kb rustdocs | 70 | import_candidate: ImportCandidate, |
77 | enum ImportCandidate { | 71 | module_with_name_to_import: Module, |
78 | UnqualifiedName(ast::NameRef), | 72 | syntax_under_caret: SyntaxNode, |
79 | QualifierStart(ast::NameRef), | ||
80 | TraitFunction(Adt, ast::PathSegment), | ||
81 | } | 73 | } |
82 | 74 | ||
83 | impl ImportCandidate { | 75 | impl AutoImportAssets { |
84 | // TODO kb refactor this mess | 76 | fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> { |
85 | fn new( | 77 | let syntax_under_caret = method_call.syntax().to_owned(); |
86 | path_under_caret: &ast::Path, | 78 | let source_analyzer = ctx.source_analyzer(&syntax_under_caret, None); |
87 | source_analyzer: &SourceAnalyzer, | 79 | let module_with_name_to_import = source_analyzer.module()?; |
88 | db: &impl HirDatabase, | 80 | Some(Self { |
89 | ) -> Option<Self> { | 81 | import_candidate: ImportCandidate::for_method_call( |
90 | if source_analyzer.resolve_path(db, path_under_caret).is_some() { | 82 | &method_call, |
83 | &source_analyzer, | ||
84 | ctx.db, | ||
85 | )?, | ||
86 | module_with_name_to_import, | ||
87 | syntax_under_caret, | ||
88 | }) | ||
89 | } | ||
90 | |||
91 | fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option<Self> { | ||
92 | let syntax_under_caret = path_under_caret.syntax().to_owned(); | ||
93 | if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { | ||
91 | return None; | 94 | return None; |
92 | } | 95 | } |
93 | 96 | ||
94 | let segment = path_under_caret.segment()?; | 97 | let source_analyzer = ctx.source_analyzer(&syntax_under_caret, None); |
95 | if let Some(qualifier) = path_under_caret.qualifier() { | 98 | let module_with_name_to_import = source_analyzer.module()?; |
96 | let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; | 99 | Some(Self { |
97 | let qualifier_start_path = | 100 | import_candidate: ImportCandidate::for_regular_path( |
98 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; | 101 | &path_under_caret, |
99 | if let Some(qualifier_start_resolution) = | 102 | &source_analyzer, |
100 | source_analyzer.resolve_path(db, &qualifier_start_path) | 103 | ctx.db, |
101 | { | 104 | )?, |
102 | let qualifier_resolution = if &qualifier_start_path == path_under_caret { | 105 | module_with_name_to_import, |
103 | qualifier_start_resolution | 106 | syntax_under_caret, |
104 | } else { | 107 | }) |
105 | source_analyzer.resolve_path(db, &qualifier)? | ||
106 | }; | ||
107 | if let PathResolution::Def(ModuleDef::Adt(function_callee)) = qualifier_resolution { | ||
108 | Some(ImportCandidate::TraitFunction(function_callee, segment)) | ||
109 | } else { | ||
110 | None | ||
111 | } | ||
112 | } else { | ||
113 | Some(ImportCandidate::QualifierStart(qualifier_start)) | ||
114 | } | ||
115 | } else { | ||
116 | if source_analyzer.resolve_path(db, path_under_caret).is_none() { | ||
117 | Some(ImportCandidate::UnqualifiedName( | ||
118 | segment.syntax().descendants().find_map(ast::NameRef::cast)?, | ||
119 | )) | ||
120 | } else { | ||
121 | None | ||
122 | } | ||
123 | } | ||
124 | } | 108 | } |
125 | 109 | ||
126 | fn get_search_query(&self) -> String { | 110 | fn get_search_query(&self) -> String { |
127 | match self { | 111 | match &self.import_candidate { |
128 | ImportCandidate::UnqualifiedName(name_ref) | 112 | ImportCandidate::UnqualifiedName(name_ref) |
129 | | ImportCandidate::QualifierStart(name_ref) => name_ref.syntax().to_string(), | 113 | | ImportCandidate::QualifierStart(name_ref) => name_ref.syntax().to_string(), |
130 | ImportCandidate::TraitFunction(_, trait_function) => { | 114 | ImportCandidate::TraitFunction(_, trait_function) => { |
131 | trait_function.syntax().to_string() | 115 | trait_function.syntax().to_string() |
132 | } | 116 | } |
117 | ImportCandidate::TraitMethod(_, trait_method) => trait_method.syntax().to_string(), | ||
133 | } | 118 | } |
134 | } | 119 | } |
135 | 120 | ||
@@ -141,7 +126,7 @@ impl ImportCandidate { | |||
141 | ImportsLocator::new(db) | 126 | ImportsLocator::new(db) |
142 | .find_imports(&self.get_search_query()) | 127 | .find_imports(&self.get_search_query()) |
143 | .into_iter() | 128 | .into_iter() |
144 | .map(|module_def| match self { | 129 | .map(|module_def| match &self.import_candidate { |
145 | ImportCandidate::TraitFunction(function_callee, _) => { | 130 | ImportCandidate::TraitFunction(function_callee, _) => { |
146 | let mut applicable_traits = Vec::new(); | 131 | let mut applicable_traits = Vec::new(); |
147 | if let ModuleDef::Function(located_function) = module_def { | 132 | if let ModuleDef::Function(located_function) = module_def { |
@@ -154,7 +139,7 @@ impl ImportCandidate { | |||
154 | .map(|trait_candidate| trait_candidate.into()) | 139 | .map(|trait_candidate| trait_candidate.into()) |
155 | .collect(); | 140 | .collect(); |
156 | 141 | ||
157 | function_callee.ty(db).iterate_path_candidates( | 142 | function_callee.iterate_path_candidates( |
158 | db, | 143 | db, |
159 | module_with_name_to_import.krate(), | 144 | module_with_name_to_import.krate(), |
160 | &trait_candidates, | 145 | &trait_candidates, |
@@ -172,6 +157,42 @@ impl ImportCandidate { | |||
172 | } | 157 | } |
173 | applicable_traits | 158 | applicable_traits |
174 | } | 159 | } |
160 | ImportCandidate::TraitMethod(function_callee, _) => { | ||
161 | let mut applicable_traits = Vec::new(); | ||
162 | if let ModuleDef::Function(located_function) = module_def { | ||
163 | let trait_candidates: FxHashSet<_> = Self::get_trait_candidates( | ||
164 | db, | ||
165 | located_function, | ||
166 | module_with_name_to_import.krate(), | ||
167 | ) | ||
168 | .into_iter() | ||
169 | .map(|trait_candidate| trait_candidate.into()) | ||
170 | .collect(); | ||
171 | |||
172 | if !trait_candidates.is_empty() { | ||
173 | function_callee.iterate_method_candidates( | ||
174 | db, | ||
175 | module_with_name_to_import.krate(), | ||
176 | &trait_candidates, | ||
177 | None, | ||
178 | |_, funciton| { | ||
179 | if let AssocContainerId::TraitId(trait_id) = | ||
180 | funciton.container(db) | ||
181 | { | ||
182 | applicable_traits.push( | ||
183 | module_with_name_to_import.find_use_path( | ||
184 | db, | ||
185 | ModuleDef::Trait(trait_id.into()), | ||
186 | ), | ||
187 | ); | ||
188 | }; | ||
189 | None::<()> | ||
190 | }, | ||
191 | ); | ||
192 | } | ||
193 | } | ||
194 | applicable_traits | ||
195 | } | ||
175 | _ => vec![module_with_name_to_import.find_use_path(db, module_def)], | 196 | _ => vec![module_with_name_to_import.find_use_path(db, module_def)], |
176 | }) | 197 | }) |
177 | .flatten() | 198 | .flatten() |
@@ -186,7 +207,6 @@ impl ImportCandidate { | |||
186 | called_function: Function, | 207 | called_function: Function, |
187 | root_crate: Crate, | 208 | root_crate: Crate, |
188 | ) -> FxHashSet<Trait> { | 209 | ) -> FxHashSet<Trait> { |
189 | let mut source_binder = SourceBinder::new(db); | ||
190 | root_crate | 210 | root_crate |
191 | .dependencies(db) | 211 | .dependencies(db) |
192 | .into_iter() | 212 | .into_iter() |
@@ -196,28 +216,22 @@ impl ImportCandidate { | |||
196 | crate_def_map | 216 | crate_def_map |
197 | .modules | 217 | .modules |
198 | .iter() | 218 | .iter() |
199 | .filter_map(|(_, module_data)| module_data.declaration_source(db)) | 219 | .map(|(_, module_data)| { |
200 | .filter_map(|in_file_module| { | 220 | let mut traits = Vec::new(); |
201 | Some((in_file_module.file_id, in_file_module.value.item_list()?.items())) | 221 | for module_def_id in module_data.scope.declarations() { |
202 | }) | 222 | if let ModuleDef::Trait(trait_candidate) = module_def_id.into() { |
203 | .map(|(file_id, item_list)| { | 223 | if trait_candidate |
204 | let mut if_file_trait_defs = Vec::new(); | 224 | .items(db) |
205 | for module_item in item_list { | 225 | .into_iter() |
206 | if let ModuleItem::TraitDef(trait_def) = module_item { | 226 | .any(|item| item == AssocItem::Function(called_function)) |
207 | if let Some(item_list) = trait_def.item_list() { | 227 | { |
208 | if item_list | 228 | traits.push(trait_candidate) |
209 | .functions() | ||
210 | .any(|fn_def| fn_def == called_function.source(db).value) | ||
211 | { | ||
212 | if_file_trait_defs.push(InFile::new(file_id, trait_def)) | ||
213 | } | ||
214 | } | 229 | } |
215 | } | 230 | } |
216 | } | 231 | } |
217 | if_file_trait_defs | 232 | traits |
218 | }) | 233 | }) |
219 | .flatten() | 234 | .flatten() |
220 | .filter_map(|in_file_trait_def| source_binder.to_def(in_file_trait_def)) | ||
221 | .collect::<FxHashSet<_>>() | 235 | .collect::<FxHashSet<_>>() |
222 | }) | 236 | }) |
223 | .flatten() | 237 | .flatten() |
@@ -225,6 +239,72 @@ impl ImportCandidate { | |||
225 | } | 239 | } |
226 | } | 240 | } |
227 | 241 | ||
242 | #[derive(Debug)] | ||
243 | // TODO kb rustdocs | ||
244 | enum ImportCandidate { | ||
245 | UnqualifiedName(ast::NameRef), | ||
246 | QualifierStart(ast::NameRef), | ||
247 | TraitFunction(Type, ast::PathSegment), | ||
248 | TraitMethod(Type, ast::NameRef), | ||
249 | } | ||
250 | |||
251 | impl ImportCandidate { | ||
252 | fn for_method_call( | ||
253 | method_call: &ast::MethodCallExpr, | ||
254 | source_analyzer: &SourceAnalyzer, | ||
255 | db: &impl HirDatabase, | ||
256 | ) -> Option<Self> { | ||
257 | if source_analyzer.resolve_method_call(method_call).is_some() { | ||
258 | return None; | ||
259 | } | ||
260 | Some(Self::TraitMethod( | ||
261 | source_analyzer.type_of(db, &method_call.expr()?)?, | ||
262 | method_call.name_ref()?, | ||
263 | )) | ||
264 | } | ||
265 | |||
266 | fn for_regular_path( | ||
267 | path_under_caret: &ast::Path, | ||
268 | source_analyzer: &SourceAnalyzer, | ||
269 | db: &impl HirDatabase, | ||
270 | ) -> Option<Self> { | ||
271 | if source_analyzer.resolve_path(db, path_under_caret).is_some() { | ||
272 | return None; | ||
273 | } | ||
274 | |||
275 | let segment = path_under_caret.segment()?; | ||
276 | if let Some(qualifier) = path_under_caret.qualifier() { | ||
277 | let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; | ||
278 | let qualifier_start_path = | ||
279 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; | ||
280 | if let Some(qualifier_start_resolution) = | ||
281 | source_analyzer.resolve_path(db, &qualifier_start_path) | ||
282 | { | ||
283 | let qualifier_resolution = if &qualifier_start_path == path_under_caret { | ||
284 | qualifier_start_resolution | ||
285 | } else { | ||
286 | source_analyzer.resolve_path(db, &qualifier)? | ||
287 | }; | ||
288 | if let PathResolution::Def(ModuleDef::Adt(function_callee)) = qualifier_resolution { | ||
289 | Some(ImportCandidate::TraitFunction(function_callee.ty(db), segment)) | ||
290 | } else { | ||
291 | None | ||
292 | } | ||
293 | } else { | ||
294 | Some(ImportCandidate::QualifierStart(qualifier_start)) | ||
295 | } | ||
296 | } else { | ||
297 | if source_analyzer.resolve_path(db, path_under_caret).is_none() { | ||
298 | Some(ImportCandidate::UnqualifiedName( | ||
299 | segment.syntax().descendants().find_map(ast::NameRef::cast)?, | ||
300 | )) | ||
301 | } else { | ||
302 | None | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | } | ||
307 | |||
228 | #[cfg(test)] | 308 | #[cfg(test)] |
229 | mod tests { | 309 | mod tests { |
230 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 310 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; |
@@ -525,32 +605,25 @@ mod tests { | |||
525 | } | 605 | } |
526 | 606 | ||
527 | #[test] | 607 | #[test] |
528 | fn not_applicable_for_imported_trait() { | 608 | fn not_applicable_for_imported_trait_for_function() { |
529 | check_assist_not_applicable( | 609 | check_assist_not_applicable( |
530 | auto_import, | 610 | auto_import, |
531 | r" | 611 | r" |
532 | mod test_mod { | 612 | mod test_mod { |
533 | pub trait TestTrait { | 613 | pub trait TestTrait { |
534 | fn test_method(&self); | ||
535 | fn test_function(); | 614 | fn test_function(); |
536 | } | 615 | } |
537 | |||
538 | pub trait TestTrait2 { | 616 | pub trait TestTrait2 { |
539 | fn test_method(&self); | ||
540 | fn test_function(); | 617 | fn test_function(); |
541 | } | 618 | } |
542 | pub enum TestEnum { | 619 | pub enum TestEnum { |
543 | One, | 620 | One, |
544 | Two, | 621 | Two, |
545 | } | 622 | } |
546 | |||
547 | impl TestTrait2 for TestEnum { | 623 | impl TestTrait2 for TestEnum { |
548 | fn test_method(&self) {} | ||
549 | fn test_function() {} | 624 | fn test_function() {} |
550 | } | 625 | } |
551 | |||
552 | impl TestTrait for TestEnum { | 626 | impl TestTrait for TestEnum { |
553 | fn test_method(&self) {} | ||
554 | fn test_function() {} | 627 | fn test_function() {} |
555 | } | 628 | } |
556 | } | 629 | } |
@@ -580,7 +653,7 @@ mod tests { | |||
580 | 653 | ||
581 | fn main() { | 654 | fn main() { |
582 | let test_struct = test_mod::TestStruct {}; | 655 | let test_struct = test_mod::TestStruct {}; |
583 | test_struct.test_method<|> | 656 | test_struct.test_meth<|>od() |
584 | } | 657 | } |
585 | ", | 658 | ", |
586 | r" | 659 | r" |
@@ -598,9 +671,42 @@ mod tests { | |||
598 | 671 | ||
599 | fn main() { | 672 | fn main() { |
600 | let test_struct = test_mod::TestStruct {}; | 673 | let test_struct = test_mod::TestStruct {}; |
601 | test_struct.test_method<|> | 674 | test_struct.test_meth<|>od() |
602 | } | 675 | } |
603 | ", | 676 | ", |
604 | ); | 677 | ); |
605 | } | 678 | } |
679 | |||
680 | #[test] | ||
681 | fn not_applicable_for_imported_trait_for_method() { | ||
682 | check_assist_not_applicable( | ||
683 | auto_import, | ||
684 | r" | ||
685 | mod test_mod { | ||
686 | pub trait TestTrait { | ||
687 | fn test_method(&self); | ||
688 | } | ||
689 | pub trait TestTrait2 { | ||
690 | fn test_method(&self); | ||
691 | } | ||
692 | pub enum TestEnum { | ||
693 | One, | ||
694 | Two, | ||
695 | } | ||
696 | impl TestTrait2 for TestEnum { | ||
697 | fn test_method(&self) {} | ||
698 | } | ||
699 | impl TestTrait for TestEnum { | ||
700 | fn test_method(&self) {} | ||
701 | } | ||
702 | } | ||
703 | |||
704 | use test_mod::TestTrait2; | ||
705 | fn main() { | ||
706 | let one = test_mod::TestEnum::One; | ||
707 | one.test<|>_method(); | ||
708 | } | ||
709 | ", | ||
710 | ) | ||
711 | } | ||
606 | } | 712 | } |