aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-02-13 11:37:32 +0000
committerGitHub <[email protected]>2020-02-13 11:37:32 +0000
commit1f897d1c6eaa7f0251bb47a5d2d1894cd8e82d54 (patch)
tree87c4f024bfd4308e4ab6a7068102808efec888b6 /crates
parent6f2cab1368dafc5663bb591a45062280a94a4ef9 (diff)
parente4f4cd77a07918529cd274f3299a020cbc884974 (diff)
Merge #3120
3120: Support trait auto import r=matklad a=SomeoneToIgnore Unfortunately, for real cases it does not work as spectacular as in the tests. The main reason for that is type inference: * The callee type [here](https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_hir_ty/src/method_resolution.rs#L369) is unknown for many cases * The trait solution [here](https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_hir_ty/src/method_resolution.rs#L399) is also often ambiguous That results in trait candidates being rejected, and some real cases not supported. Example: no imports for `String::from_str("test")` Co-authored-by: Kirill Bulatov <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs588
-rw-r--r--crates/ra_hir/src/code_model.rs2
2 files changed, 545 insertions, 45 deletions
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index 1fb701da5..c4aea2a06 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -1,10 +1,18 @@
1use ra_ide_db::imports_locator::ImportsLocator;
2use ra_syntax::ast::{self, AstNode};
3
4use crate::{ 1use crate::{
5 assist_ctx::{Assist, AssistCtx}, 2 assist_ctx::{Assist, AssistCtx},
6 insert_use_statement, AssistId, 3 insert_use_statement, AssistId,
7}; 4};
5use hir::{
6 db::HirDatabase, AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution,
7 SourceAnalyzer, Trait, Type,
8};
9use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase};
10use ra_prof::profile;
11use ra_syntax::{
12 ast::{self, AstNode},
13 SyntaxNode,
14};
15use rustc_hash::FxHashSet;
8use std::collections::BTreeSet; 16use std::collections::BTreeSet;
9 17
10// Assist: auto_import 18// Assist: auto_import
@@ -27,52 +35,24 @@ use std::collections::BTreeSet;
27// # pub mod std { pub mod collections { pub struct HashMap { } } } 35// # pub mod std { pub mod collections { pub struct HashMap { } } }
28// ``` 36// ```
29pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { 37pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
30 let path_under_caret: ast::Path = ctx.find_node_at_offset()?; 38 let auto_import_assets = AutoImportAssets::new(&ctx)?;
31 if path_under_caret.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { 39 let proposed_imports = auto_import_assets.search_for_imports(ctx.db);
32 return None;
33 }
34
35 let module = path_under_caret.syntax().ancestors().find_map(ast::Module::cast);
36 let position = match module.and_then(|it| it.item_list()) {
37 Some(item_list) => item_list.syntax().clone(),
38 None => {
39 let current_file =
40 path_under_caret.syntax().ancestors().find_map(ast::SourceFile::cast)?;
41 current_file.syntax().clone()
42 }
43 };
44 let source_analyzer = ctx.source_analyzer(&position, None);
45 let module_with_name_to_import = source_analyzer.module()?;
46
47 let name_ref_to_import =
48 path_under_caret.syntax().descendants().find_map(ast::NameRef::cast)?;
49 if source_analyzer
50 .resolve_path(ctx.db, &name_ref_to_import.syntax().ancestors().find_map(ast::Path::cast)?)
51 .is_some()
52 {
53 return None;
54 }
55
56 let name_to_import = name_ref_to_import.syntax().to_string();
57 let proposed_imports = ImportsLocator::new(ctx.db)
58 .find_imports(&name_to_import)
59 .into_iter()
60 .filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def))
61 .filter(|use_path| !use_path.segments.is_empty())
62 .take(20)
63 .collect::<BTreeSet<_>>();
64
65 if proposed_imports.is_empty() { 40 if proposed_imports.is_empty() {
66 return None; 41 return None;
67 } 42 }
68 43
69 let mut group = ctx.add_assist_group(format!("Import {}", name_to_import)); 44 let assist_group_name = if proposed_imports.len() == 1 {
45 format!("Import `{}`", proposed_imports.iter().next().unwrap())
46 } else {
47 auto_import_assets.get_import_group_message()
48 };
49 let mut group = ctx.add_assist_group(assist_group_name);
70 for import in proposed_imports { 50 for import in proposed_imports {
71 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { 51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
72 edit.target(path_under_caret.syntax().text_range()); 52 edit.target(auto_import_assets.syntax_under_caret.text_range());
73 insert_use_statement( 53 insert_use_statement(
74 &position, 54 &auto_import_assets.syntax_under_caret,
75 path_under_caret.syntax(), 55 &auto_import_assets.syntax_under_caret,
76 &import, 56 &import,
77 edit.text_edit_builder(), 57 edit.text_edit_builder(),
78 ); 58 );
@@ -81,11 +61,232 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
81 group.finish() 61 group.finish()
82} 62}
83 63
64struct AutoImportAssets {
65 import_candidate: ImportCandidate,
66 module_with_name_to_import: Module,
67 syntax_under_caret: SyntaxNode,
68}
69
70impl AutoImportAssets {
71 fn new(ctx: &AssistCtx) -> Option<Self> {
72 if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() {
73 Self::for_regular_path(path_under_caret, &ctx)
74 } else {
75 Self::for_method_call(ctx.find_node_at_offset()?, &ctx)
76 }
77 }
78
79 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> {
80 let syntax_under_caret = method_call.syntax().to_owned();
81 let source_analyzer = ctx.source_analyzer(&syntax_under_caret, None);
82 let module_with_name_to_import = source_analyzer.module()?;
83 Some(Self {
84 import_candidate: ImportCandidate::for_method_call(
85 &method_call,
86 &source_analyzer,
87 ctx.db,
88 )?,
89 module_with_name_to_import,
90 syntax_under_caret,
91 })
92 }
93
94 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option<Self> {
95 let syntax_under_caret = path_under_caret.syntax().to_owned();
96 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() {
97 return None;
98 }
99
100 let source_analyzer = ctx.source_analyzer(&syntax_under_caret, None);
101 let module_with_name_to_import = source_analyzer.module()?;
102 Some(Self {
103 import_candidate: ImportCandidate::for_regular_path(
104 &path_under_caret,
105 &source_analyzer,
106 ctx.db,
107 )?,
108 module_with_name_to_import,
109 syntax_under_caret,
110 })
111 }
112
113 fn get_search_query(&self) -> &str {
114 match &self.import_candidate {
115 ImportCandidate::UnqualifiedName(name) => name,
116 ImportCandidate::QualifierStart(qualifier_start) => qualifier_start,
117 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name,
118 ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name,
119 }
120 }
121
122 fn get_import_group_message(&self) -> String {
123 match &self.import_candidate {
124 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
125 ImportCandidate::QualifierStart(qualifier_start) => {
126 format!("Import {}", qualifier_start)
127 }
128 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => {
129 format!("Import a trait for item {}", trait_assoc_item_name)
130 }
131 ImportCandidate::TraitMethod(_, trait_method_name) => {
132 format!("Import a trait for method {}", trait_method_name)
133 }
134 }
135 }
136
137 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> {
138 let _p = profile("auto_import::search_for_imports");
139 let current_crate = self.module_with_name_to_import.krate();
140 ImportsLocator::new(db)
141 .find_imports(&self.get_search_query())
142 .into_iter()
143 .filter_map(|module_def| match &self.import_candidate {
144 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
145 let located_assoc_item = match module_def {
146 ModuleDef::Function(located_function) => located_function
147 .as_assoc_item(db)
148 .map(|assoc| assoc.container(db))
149 .and_then(Self::assoc_to_trait),
150 ModuleDef::Const(located_const) => located_const
151 .as_assoc_item(db)
152 .map(|assoc| assoc.container(db))
153 .and_then(Self::assoc_to_trait),
154 _ => None,
155 }?;
156
157 let mut trait_candidates = FxHashSet::default();
158 trait_candidates.insert(located_assoc_item.into());
159
160 assoc_item_type
161 .iterate_path_candidates(
162 db,
163 current_crate,
164 &trait_candidates,
165 None,
166 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
167 )
168 .map(ModuleDef::from)
169 }
170 ImportCandidate::TraitMethod(function_callee, _) => {
171 let located_assoc_item =
172 if let ModuleDef::Function(located_function) = module_def {
173 located_function
174 .as_assoc_item(db)
175 .map(|assoc| assoc.container(db))
176 .and_then(Self::assoc_to_trait)
177 } else {
178 None
179 }?;
180
181 let mut trait_candidates = FxHashSet::default();
182 trait_candidates.insert(located_assoc_item.into());
183
184 function_callee
185 .iterate_method_candidates(
186 db,
187 current_crate,
188 &trait_candidates,
189 None,
190 |_, function| {
191 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
192 },
193 )
194 .map(ModuleDef::from)
195 }
196 _ => Some(module_def),
197 })
198 .filter_map(|module_def| self.module_with_name_to_import.find_use_path(db, module_def))
199 .filter(|use_path| !use_path.segments.is_empty())
200 .take(20)
201 .collect::<BTreeSet<_>>()
202 }
203
204 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<Trait> {
205 if let AssocItemContainer::Trait(extracted_trait) = assoc {
206 Some(extracted_trait)
207 } else {
208 None
209 }
210 }
211}
212
213#[derive(Debug)]
214enum ImportCandidate {
215 /// Simple name like 'HashMap'
216 UnqualifiedName(String),
217 /// First part of the qualified name.
218 /// For 'std::collections::HashMap', that will be 'std'.
219 QualifierStart(String),
220 /// A trait associated function (with no self parameter) or associated constant.
221 /// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type
222 /// and `String` is the `test_function`
223 TraitAssocItem(Type, String),
224 /// A trait method with self parameter.
225 /// For 'test_enum.test_method()', `Type` is the `test_enum` expression type
226 /// and `String` is the `test_method`
227 TraitMethod(Type, String),
228}
229
230impl ImportCandidate {
231 fn for_method_call(
232 method_call: &ast::MethodCallExpr,
233 source_analyzer: &SourceAnalyzer,
234 db: &impl HirDatabase,
235 ) -> Option<Self> {
236 if source_analyzer.resolve_method_call(method_call).is_some() {
237 return None;
238 }
239 Some(Self::TraitMethod(
240 source_analyzer.type_of(db, &method_call.expr()?)?,
241 method_call.name_ref()?.syntax().to_string(),
242 ))
243 }
244
245 fn for_regular_path(
246 path_under_caret: &ast::Path,
247 source_analyzer: &SourceAnalyzer,
248 db: &impl HirDatabase,
249 ) -> Option<Self> {
250 if source_analyzer.resolve_path(db, path_under_caret).is_some() {
251 return None;
252 }
253
254 let segment = path_under_caret.segment()?;
255 if let Some(qualifier) = path_under_caret.qualifier() {
256 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
257 let qualifier_start_path =
258 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
259 if let Some(qualifier_start_resolution) =
260 source_analyzer.resolve_path(db, &qualifier_start_path)
261 {
262 let qualifier_resolution = if qualifier_start_path == qualifier {
263 qualifier_start_resolution
264 } else {
265 source_analyzer.resolve_path(db, &qualifier)?
266 };
267 if let PathResolution::Def(ModuleDef::Adt(assoc_item_path)) = qualifier_resolution {
268 Some(ImportCandidate::TraitAssocItem(
269 assoc_item_path.ty(db),
270 segment.syntax().to_string(),
271 ))
272 } else {
273 None
274 }
275 } else {
276 Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string()))
277 }
278 } else {
279 Some(ImportCandidate::UnqualifiedName(
280 segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(),
281 ))
282 }
283 }
284}
285
84#[cfg(test)] 286#[cfg(test)]
85mod tests { 287mod tests {
86 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
87
88 use super::*; 288 use super::*;
289 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
89 290
90 #[test] 291 #[test]
91 fn applicable_when_found_an_import() { 292 fn applicable_when_found_an_import() {
@@ -290,4 +491,303 @@ mod tests {
290 ", 491 ",
291 ); 492 );
292 } 493 }
494
495 #[test]
496 fn not_applicable_for_imported_function() {
497 check_assist_not_applicable(
498 auto_import,
499 r"
500 pub mod test_mod {
501 pub fn test_function() {}
502 }
503
504 use test_mod::test_function;
505 fn main() {
506 test_function<|>
507 }
508 ",
509 );
510 }
511
512 #[test]
513 fn associated_struct_function() {
514 check_assist(
515 auto_import,
516 r"
517 mod test_mod {
518 pub struct TestStruct {}
519 impl TestStruct {
520 pub fn test_function() {}
521 }
522 }
523
524 fn main() {
525 TestStruct::test_function<|>
526 }
527 ",
528 r"
529 use test_mod::TestStruct;
530
531 mod test_mod {
532 pub struct TestStruct {}
533 impl TestStruct {
534 pub fn test_function() {}
535 }
536 }
537
538 fn main() {
539 TestStruct::test_function<|>
540 }
541 ",
542 );
543 }
544
545 #[test]
546 fn associated_struct_const() {
547 check_assist(
548 auto_import,
549 r"
550 mod test_mod {
551 pub struct TestStruct {}
552 impl TestStruct {
553 const TEST_CONST: u8 = 42;
554 }
555 }
556
557 fn main() {
558 TestStruct::TEST_CONST<|>
559 }
560 ",
561 r"
562 use test_mod::TestStruct;
563
564 mod test_mod {
565 pub struct TestStruct {}
566 impl TestStruct {
567 const TEST_CONST: u8 = 42;
568 }
569 }
570
571 fn main() {
572 TestStruct::TEST_CONST<|>
573 }
574 ",
575 );
576 }
577
578 #[test]
579 fn associated_trait_function() {
580 check_assist(
581 auto_import,
582 r"
583 mod test_mod {
584 pub trait TestTrait {
585 fn test_function();
586 }
587 pub struct TestStruct {}
588 impl TestTrait for TestStruct {
589 fn test_function() {}
590 }
591 }
592
593 fn main() {
594 test_mod::TestStruct::test_function<|>
595 }
596 ",
597 r"
598 use test_mod::TestTrait;
599
600 mod test_mod {
601 pub trait TestTrait {
602 fn test_function();
603 }
604 pub struct TestStruct {}
605 impl TestTrait for TestStruct {
606 fn test_function() {}
607 }
608 }
609
610 fn main() {
611 test_mod::TestStruct::test_function<|>
612 }
613 ",
614 );
615 }
616
617 #[test]
618 fn not_applicable_for_imported_trait_for_function() {
619 check_assist_not_applicable(
620 auto_import,
621 r"
622 mod test_mod {
623 pub trait TestTrait {
624 fn test_function();
625 }
626 pub trait TestTrait2 {
627 fn test_function();
628 }
629 pub enum TestEnum {
630 One,
631 Two,
632 }
633 impl TestTrait2 for TestEnum {
634 fn test_function() {}
635 }
636 impl TestTrait for TestEnum {
637 fn test_function() {}
638 }
639 }
640
641 use test_mod::TestTrait2;
642 fn main() {
643 test_mod::TestEnum::test_function<|>;
644 }
645 ",
646 )
647 }
648
649 #[test]
650 fn associated_trait_const() {
651 check_assist(
652 auto_import,
653 r"
654 mod test_mod {
655 pub trait TestTrait {
656 const TEST_CONST: u8;
657 }
658 pub struct TestStruct {}
659 impl TestTrait for TestStruct {
660 const TEST_CONST: u8 = 42;
661 }
662 }
663
664 fn main() {
665 test_mod::TestStruct::TEST_CONST<|>
666 }
667 ",
668 r"
669 use test_mod::TestTrait;
670
671 mod test_mod {
672 pub trait TestTrait {
673 const TEST_CONST: u8;
674 }
675 pub struct TestStruct {}
676 impl TestTrait for TestStruct {
677 const TEST_CONST: u8 = 42;
678 }
679 }
680
681 fn main() {
682 test_mod::TestStruct::TEST_CONST<|>
683 }
684 ",
685 );
686 }
687
688 #[test]
689 fn not_applicable_for_imported_trait_for_const() {
690 check_assist_not_applicable(
691 auto_import,
692 r"
693 mod test_mod {
694 pub trait TestTrait {
695 const TEST_CONST: u8;
696 }
697 pub trait TestTrait2 {
698 const TEST_CONST: f64;
699 }
700 pub enum TestEnum {
701 One,
702 Two,
703 }
704 impl TestTrait2 for TestEnum {
705 const TEST_CONST: f64 = 42.0;
706 }
707 impl TestTrait for TestEnum {
708 const TEST_CONST: u8 = 42;
709 }
710 }
711
712 use test_mod::TestTrait2;
713 fn main() {
714 test_mod::TestEnum::TEST_CONST<|>;
715 }
716 ",
717 )
718 }
719
720 #[test]
721 fn trait_method() {
722 check_assist(
723 auto_import,
724 r"
725 mod test_mod {
726 pub trait TestTrait {
727 fn test_method(&self);
728 }
729 pub struct TestStruct {}
730 impl TestTrait for TestStruct {
731 fn test_method(&self) {}
732 }
733 }
734
735 fn main() {
736 let test_struct = test_mod::TestStruct {};
737 test_struct.test_meth<|>od()
738 }
739 ",
740 r"
741 use test_mod::TestTrait;
742
743 mod test_mod {
744 pub trait TestTrait {
745 fn test_method(&self);
746 }
747 pub struct TestStruct {}
748 impl TestTrait for TestStruct {
749 fn test_method(&self) {}
750 }
751 }
752
753 fn main() {
754 let test_struct = test_mod::TestStruct {};
755 test_struct.test_meth<|>od()
756 }
757 ",
758 );
759 }
760
761 #[test]
762 fn not_applicable_for_imported_trait_for_method() {
763 check_assist_not_applicable(
764 auto_import,
765 r"
766 mod test_mod {
767 pub trait TestTrait {
768 fn test_method(&self);
769 }
770 pub trait TestTrait2 {
771 fn test_method(&self);
772 }
773 pub enum TestEnum {
774 One,
775 Two,
776 }
777 impl TestTrait2 for TestEnum {
778 fn test_method(&self) {}
779 }
780 impl TestTrait for TestEnum {
781 fn test_method(&self) {}
782 }
783 }
784
785 use test_mod::TestTrait2;
786 fn main() {
787 let one = test_mod::TestEnum::One;
788 one.test<|>_method();
789 }
790 ",
791 )
792 }
293} 793}
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index 4fb679f6d..a56b8ab04 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -818,7 +818,7 @@ impl TypeParam {
818 } 818 }
819} 819}
820 820
821// FIXME: rename to `ImplBlock` 821// FIXME: rename from `ImplBlock` to `Impl`
822#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 822#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
823pub struct ImplBlock { 823pub struct ImplBlock {
824 pub(crate) id: ImplId, 824 pub(crate) id: ImplId,