aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorKirill Bulatov <[email protected]>2020-02-11 16:24:43 +0000
committerKirill Bulatov <[email protected]>2020-02-12 15:18:42 +0000
commitd5c3808545e26d246d75e0754e81de803f9e53e6 (patch)
treecaf2376b06ace612efb274312825251a2c2ab53c /crates
parent8f959f20ee0fecd644054ffed334c378f9ae20f5 (diff)
Support trait method call autoimports
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs306
-rw-r--r--crates/ra_hir/src/code_model.rs6
2 files changed, 211 insertions, 101 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 @@
1use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase}; 1use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase};
2use ra_syntax::ast::{self, AstNode}; 2use ra_syntax::{
3 ast::{self, AstNode},
4 SyntaxNode,
5};
3 6
4use crate::{ 7use crate::{
5 assist_ctx::{Assist, AssistCtx}, 8 assist_ctx::{Assist, AssistCtx},
6 insert_use_statement, AssistId, 9 insert_use_statement, AssistId,
7}; 10};
8use ast::{FnDefOwner, ModuleItem, ModuleItemOwner};
9use hir::{ 11use 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};
14use rustc_hash::FxHashSet; 16use rustc_hash::FxHashSet;
15use std::collections::BTreeSet; 17use 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// ```
36pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { 38pub(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)] 69struct AutoImportAssets {
76// TODO kb rustdocs 70 import_candidate: ImportCandidate,
77enum 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
83impl ImportCandidate { 75impl 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
244enum ImportCandidate {
245 UnqualifiedName(ast::NameRef),
246 QualifierStart(ast::NameRef),
247 TraitFunction(Type, ast::PathSegment),
248 TraitMethod(Type, ast::NameRef),
249}
250
251impl 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)]
229mod tests { 309mod 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}
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index 73158b8bd..140b3a87f 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -548,6 +548,10 @@ impl Function {
548 let mut validator = ExprValidator::new(self.id, infer, sink); 548 let mut validator = ExprValidator::new(self.id, infer, sink);
549 validator.validate_body(db); 549 validator.validate_body(db);
550 } 550 }
551
552 pub fn container(self, db: &impl DefDatabase) -> AssocContainerId {
553 self.id.lookup(db).container
554 }
551} 555}
552 556
553#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 557#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -699,7 +703,7 @@ impl AssocItem {
699 703
700 pub fn container(self, db: &impl DefDatabase) -> AssocContainerId { 704 pub fn container(self, db: &impl DefDatabase) -> AssocContainerId {
701 match self { 705 match self {
702 AssocItem::Function(f) => f.id.lookup(db).container, 706 AssocItem::Function(f) => f.container(db),
703 AssocItem::Const(c) => c.id.lookup(db).container, 707 AssocItem::Const(c) => c.id.lookup(db).container,
704 AssocItem::TypeAlias(t) => t.id.lookup(db).container, 708 AssocItem::TypeAlias(t) => t.id.lookup(db).container,
705 } 709 }