aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorKirill Bulatov <[email protected]>2021-01-05 08:34:03 +0000
committerKirill Bulatov <[email protected]>2021-01-16 18:44:12 +0000
commitdb335a1bbf1d1bea2c761f67efb4b49831738e31 (patch)
tree910963c004c460d2f0c322a0e643947aaf7132b8 /crates
parent9a349f280ff1c6d0b57df80aa3d6720474e4b00a (diff)
Add flyimport completion for trait assoc items
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/auto_import.rs41
-rw-r--r--crates/assists/src/handlers/qualify_path.rs41
-rw-r--r--crates/completion/src/completions/flyimport.rs300
-rw-r--r--crates/completion/src/config.rs2
-rw-r--r--crates/completion/src/item.rs23
-rw-r--r--crates/completion/src/lib.rs3
-rw-r--r--crates/completion/src/render.rs22
-rw-r--r--crates/completion/src/test_utils.rs2
-rw-r--r--crates/hir/src/code_model.rs16
-rw-r--r--crates/hir_def/src/import_map.rs66
-rw-r--r--crates/ide/src/doc_links.rs8
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs286
-rw-r--r--crates/ide_db/src/imports_locator.rs74
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs15
-rw-r--r--crates/rust-analyzer/src/handlers.rs5
-rw-r--r--crates/rust-analyzer/src/to_proto.rs2
18 files changed, 662 insertions, 248 deletions
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 4e2a4fcd9..e93901cb3 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -3,7 +3,7 @@ use ide_db::helpers::{
3 insert_use::{insert_use, ImportScope}, 3 insert_use::{insert_use, ImportScope},
4 mod_path_to_ast, 4 mod_path_to_ast,
5}; 5};
6use syntax::ast; 6use syntax::{ast, AstNode, SyntaxNode};
7 7
8use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; 8use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
9 9
@@ -82,25 +82,16 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
82// # pub mod std { pub mod collections { pub struct HashMap { } } } 82// # pub mod std { pub mod collections { pub struct HashMap { } } }
83// ``` 83// ```
84pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 84pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
85 let import_assets = 85 let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
86 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { 86 let proposed_imports =
87 ImportAssets::for_regular_path(path_under_caret, &ctx.sema) 87 import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
88 } else if let Some(method_under_caret) =
89 ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
90 {
91 ImportAssets::for_method_call(method_under_caret, &ctx.sema)
92 } else {
93 None
94 }?;
95 let proposed_imports = import_assets.search_for_imports(&ctx.sema, &ctx.config.insert_use);
96 if proposed_imports.is_empty() { 88 if proposed_imports.is_empty() {
97 return None; 89 return None;
98 } 90 }
99 91
100 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 92 let range = ctx.sema.original_range(&syntax_under_caret).range;
101 let group = import_group_message(import_assets.import_candidate()); 93 let group = import_group_message(import_assets.import_candidate());
102 let scope = 94 let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
103 ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?;
104 for (import, _) in proposed_imports { 95 for (import, _) in proposed_imports {
105 acc.add_group( 96 acc.add_group(
106 &group, 97 &group,
@@ -117,14 +108,28 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
117 Some(()) 108 Some(())
118} 109}
119 110
111pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets, SyntaxNode)> {
112 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
113 ImportAssets::for_exact_path(&path_under_caret, &ctx.sema)
114 .zip(Some(path_under_caret.syntax().clone()))
115 } else if let Some(method_under_caret) =
116 ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
117 {
118 ImportAssets::for_method_call(&method_under_caret, &ctx.sema)
119 .zip(Some(method_under_caret.syntax().clone()))
120 } else {
121 None
122 }
123}
124
120fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel { 125fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
121 let name = match import_candidate { 126 let name = match import_candidate {
122 ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name), 127 ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
123 ImportCandidate::TraitAssocItem(candidate) => { 128 ImportCandidate::TraitAssocItem(candidate) => {
124 format!("Import a trait for item {}", &candidate.name) 129 format!("Import a trait for item {}", candidate.name.text())
125 } 130 }
126 ImportCandidate::TraitMethod(candidate) => { 131 ImportCandidate::TraitMethod(candidate) => {
127 format!("Import a trait for method {}", &candidate.name) 132 format!("Import a trait for method {}", candidate.name.text())
128 } 133 }
129 }; 134 };
130 GroupLabel(name) 135 GroupLabel(name)
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
index a7d9fd4dc..af8a11d03 100644
--- a/crates/assists/src/handlers/qualify_path.rs
+++ b/crates/assists/src/handlers/qualify_path.rs
@@ -1,10 +1,7 @@
1use std::iter; 1use std::iter;
2 2
3use hir::AsName; 3use hir::AsName;
4use ide_db::helpers::{ 4use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast};
5 import_assets::{ImportAssets, ImportCandidate},
6 mod_path_to_ast,
7};
8use ide_db::RootDatabase; 5use ide_db::RootDatabase;
9use syntax::{ 6use syntax::{
10 ast, 7 ast,
@@ -18,6 +15,8 @@ use crate::{
18 AssistId, AssistKind, GroupLabel, 15 AssistId, AssistKind, GroupLabel,
19}; 16};
20 17
18use super::auto_import::find_importable_node;
19
21// Assist: qualify_path 20// Assist: qualify_path
22// 21//
23// If the name is unresolved, provides all possible qualified paths for it. 22// If the name is unresolved, provides all possible qualified paths for it.
@@ -36,47 +35,38 @@ use crate::{
36// # pub mod std { pub mod collections { pub struct HashMap { } } } 35// # pub mod std { pub mod collections { pub struct HashMap { } } }
37// ``` 36// ```
38pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 37pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let import_assets = 38 let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
40 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
41 ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
42 } else if let Some(method_under_caret) =
43 ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
44 {
45 ImportAssets::for_method_call(method_under_caret, &ctx.sema)
46 } else {
47 None
48 }?;
49 let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema); 39 let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
50 if proposed_imports.is_empty() { 40 if proposed_imports.is_empty() {
51 return None; 41 return None;
52 } 42 }
53 43
54 let candidate = import_assets.import_candidate(); 44 let candidate = import_assets.import_candidate();
55 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 45 let range = ctx.sema.original_range(&syntax_under_caret).range;
56 46
57 let qualify_candidate = match candidate { 47 let qualify_candidate = match candidate {
58 ImportCandidate::Path(candidate) => { 48 ImportCandidate::Path(candidate) => {
59 if candidate.qualifier.is_some() { 49 if candidate.qualifier.is_some() {
60 mark::hit!(qualify_path_qualifier_start); 50 mark::hit!(qualify_path_qualifier_start);
61 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 51 let path = ast::Path::cast(syntax_under_caret)?;
62 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); 52 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
63 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list()) 53 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
64 } else { 54 } else {
65 mark::hit!(qualify_path_unqualified_name); 55 mark::hit!(qualify_path_unqualified_name);
66 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 56 let path = ast::Path::cast(syntax_under_caret)?;
67 let generics = path.segment()?.generic_arg_list(); 57 let generics = path.segment()?.generic_arg_list();
68 QualifyCandidate::UnqualifiedName(generics) 58 QualifyCandidate::UnqualifiedName(generics)
69 } 59 }
70 } 60 }
71 ImportCandidate::TraitAssocItem(_) => { 61 ImportCandidate::TraitAssocItem(_) => {
72 mark::hit!(qualify_path_trait_assoc_item); 62 mark::hit!(qualify_path_trait_assoc_item);
73 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 63 let path = ast::Path::cast(syntax_under_caret)?;
74 let (qualifier, segment) = (path.qualifier()?, path.segment()?); 64 let (qualifier, segment) = (path.qualifier()?, path.segment()?);
75 QualifyCandidate::TraitAssocItem(qualifier, segment) 65 QualifyCandidate::TraitAssocItem(qualifier, segment)
76 } 66 }
77 ImportCandidate::TraitMethod(_) => { 67 ImportCandidate::TraitMethod(_) => {
78 mark::hit!(qualify_path_trait_method); 68 mark::hit!(qualify_path_trait_method);
79 let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?; 69 let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret)?;
80 QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr) 70 QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
81 } 71 }
82 }; 72 };
@@ -140,7 +130,7 @@ impl QualifyCandidate<'_> {
140 let generics = 130 let generics =
141 mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string); 131 mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string);
142 let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args()); 132 let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
143 let trait_ = item_as_trait(item)?; 133 let trait_ = item_as_trait(db, item)?;
144 let method = find_trait_method(db, trait_, &trait_method_name)?; 134 let method = find_trait_method(db, trait_, &trait_method_name)?;
145 if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) { 135 if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
146 let receiver = match self_access { 136 let receiver = match self_access {
@@ -179,11 +169,13 @@ fn find_trait_method(
179 } 169 }
180} 170}
181 171
182fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> { 172fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
183 if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) { 173 let item_module_def = hir::ModuleDef::from(item.as_module_def_id()?);
174
175 if let hir::ModuleDef::Trait(trait_) = item_module_def {
184 Some(trait_) 176 Some(trait_)
185 } else { 177 } else {
186 None 178 item_module_def.as_assoc_item(db)?.containing_trait(db)
187 } 179 }
188} 180}
189 181
@@ -191,7 +183,8 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
191 let name = match candidate { 183 let name = match candidate {
192 ImportCandidate::Path(it) => &it.name, 184 ImportCandidate::Path(it) => &it.name,
193 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, 185 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
194 }; 186 }
187 .text();
195 GroupLabel(format!("Qualify {}", name)) 188 GroupLabel(format!("Qualify {}", name))
196} 189}
197 190
diff --git a/crates/completion/src/completions/flyimport.rs b/crates/completion/src/completions/flyimport.rs
index 222809638..9101e405c 100644
--- a/crates/completion/src/completions/flyimport.rs
+++ b/crates/completion/src/completions/flyimport.rs
@@ -45,9 +45,8 @@
45//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding 45//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
46//! capability enabled. 46//! capability enabled.
47 47
48use either::Either;
49use hir::{ModPath, ScopeDef}; 48use hir::{ModPath, ScopeDef};
50use ide_db::{helpers::insert_use::ImportScope, imports_locator}; 49use ide_db::helpers::{import_assets::ImportAssets, insert_use::ImportScope};
51use syntax::AstNode; 50use syntax::AstNode;
52use test_utils::mark; 51use test_utils::mark;
53 52
@@ -60,7 +59,7 @@ use crate::{
60use super::Completions; 59use super::Completions;
61 60
62pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 61pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
63 if !ctx.config.enable_autoimport_completions { 62 if !ctx.config.enable_imports_on_the_fly {
64 return None; 63 return None;
65 } 64 }
66 if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() { 65 if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
@@ -72,46 +71,56 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
72 } 71 }
73 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string()); 72 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
74 73
75 let current_module = ctx.scope.module()?; 74 let import_scope =
76 let anchor = ctx.name_ref_syntax.as_ref()?; 75 ImportScope::find_insert_use_container(ctx.name_ref_syntax.as_ref()?.syntax(), &ctx.sema)?;
77 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
78
79 let user_input_lowercased = potential_import_name.to_lowercase(); 76 let user_input_lowercased = potential_import_name.to_lowercase();
80 let mut all_mod_paths = imports_locator::find_similar_imports( 77 let mut all_mod_paths = import_assets(ctx, potential_import_name)?
81 &ctx.sema, 78 .search_for_relative_paths(&ctx.sema)
82 ctx.krate?, 79 .into_iter()
83 Some(40), 80 .map(|(mod_path, item_in_ns)| {
84 potential_import_name, 81 let scope_item = match item_in_ns {
85 true, 82 hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
86 true, 83 hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
87 ) 84 hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
88 .filter_map(|import_candidate| { 85 };
89 Some(match import_candidate { 86 (mod_path, scope_item)
90 Either::Left(module_def) => {
91 (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
92 }
93 Either::Right(macro_def) => {
94 (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
95 }
96 }) 87 })
97 }) 88 .collect::<Vec<_>>();
98 .filter(|(mod_path, _)| mod_path.len() > 1)
99 .collect::<Vec<_>>();
100
101 all_mod_paths.sort_by_cached_key(|(mod_path, _)| { 89 all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
102 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) 90 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
103 }); 91 });
104 92
105 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| { 93 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
106 render_resolution_with_import( 94 let import_for_trait_assoc_item = match definition {
107 RenderContext::new(ctx), 95 ScopeDef::ModuleDef(module_def) => module_def
108 ImportEdit { import_path, import_scope: import_scope.clone() }, 96 .as_assoc_item(ctx.db)
109 &definition, 97 .and_then(|assoc| assoc.containing_trait(ctx.db))
110 ) 98 .is_some(),
99 _ => false,
100 };
101 let import_edit = ImportEdit {
102 import_path,
103 import_scope: import_scope.clone(),
104 import_for_trait_assoc_item,
105 };
106 render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
111 })); 107 }));
112 Some(()) 108 Some(())
113} 109}
114 110
111fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
112 let current_module = ctx.scope.module()?;
113 if let Some(dot_receiver) = &ctx.dot_receiver {
114 ImportAssets::for_fuzzy_method_call(
115 current_module,
116 ctx.sema.type_of_expr(dot_receiver)?,
117 fuzzy_name,
118 )
119 } else {
120 ImportAssets::for_fuzzy_path(current_module, ctx.path_qual.clone(), fuzzy_name, &ctx.sema)
121 }
122}
123
115fn compute_fuzzy_completion_order_key( 124fn compute_fuzzy_completion_order_key(
116 proposed_mod_path: &ModPath, 125 proposed_mod_path: &ModPath,
117 user_input_lowercased: &str, 126 user_input_lowercased: &str,
@@ -259,6 +268,176 @@ fn main() {
259 } 268 }
260 269
261 #[test] 270 #[test]
271 fn trait_function_fuzzy_completion() {
272 let fixture = r#"
273 //- /lib.rs crate:dep
274 pub mod test_mod {
275 pub trait TestTrait {
276 const SPECIAL_CONST: u8;
277 type HumbleType;
278 fn weird_function();
279 fn random_method(&self);
280 }
281 pub struct TestStruct {}
282 impl TestTrait for TestStruct {
283 const SPECIAL_CONST: u8 = 42;
284 type HumbleType = ();
285 fn weird_function() {}
286 fn random_method(&self) {}
287 }
288 }
289
290 //- /main.rs crate:main deps:dep
291 fn main() {
292 dep::test_mod::TestStruct::wei$0
293 }
294 "#;
295
296 check(
297 fixture,
298 expect![[r#"
299 fn weird_function() (dep::test_mod::TestTrait) fn weird_function()
300 "#]],
301 );
302
303 check_edit(
304 "weird_function",
305 fixture,
306 r#"
307use dep::test_mod::TestTrait;
308
309fn main() {
310 dep::test_mod::TestStruct::weird_function()$0
311}
312"#,
313 );
314 }
315
316 #[test]
317 fn trait_const_fuzzy_completion() {
318 let fixture = r#"
319 //- /lib.rs crate:dep
320 pub mod test_mod {
321 pub trait TestTrait {
322 const SPECIAL_CONST: u8;
323 type HumbleType;
324 fn weird_function();
325 fn random_method(&self);
326 }
327 pub struct TestStruct {}
328 impl TestTrait for TestStruct {
329 const SPECIAL_CONST: u8 = 42;
330 type HumbleType = ();
331 fn weird_function() {}
332 fn random_method(&self) {}
333 }
334 }
335
336 //- /main.rs crate:main deps:dep
337 fn main() {
338 dep::test_mod::TestStruct::spe$0
339 }
340 "#;
341
342 check(
343 fixture,
344 expect![[r#"
345 ct SPECIAL_CONST (dep::test_mod::TestTrait)
346 "#]],
347 );
348
349 check_edit(
350 "SPECIAL_CONST",
351 fixture,
352 r#"
353use dep::test_mod::TestTrait;
354
355fn main() {
356 dep::test_mod::TestStruct::SPECIAL_CONST
357}
358"#,
359 );
360 }
361
362 #[test]
363 fn trait_method_fuzzy_completion() {
364 let fixture = r#"
365 //- /lib.rs crate:dep
366 pub mod test_mod {
367 pub trait TestTrait {
368 const SPECIAL_CONST: u8;
369 type HumbleType;
370 fn weird_function();
371 fn random_method(&self);
372 }
373 pub struct TestStruct {}
374 impl TestTrait for TestStruct {
375 const SPECIAL_CONST: u8 = 42;
376 type HumbleType = ();
377 fn weird_function() {}
378 fn random_method(&self) {}
379 }
380 }
381
382 //- /main.rs crate:main deps:dep
383 fn main() {
384 let test_struct = dep::test_mod::TestStruct {};
385 test_struct.ran$0
386 }
387 "#;
388
389 check(
390 fixture,
391 expect![[r#"
392 me random_method() (dep::test_mod::TestTrait) fn random_method(&self)
393 "#]],
394 );
395
396 check_edit(
397 "random_method",
398 fixture,
399 r#"
400use dep::test_mod::TestTrait;
401
402fn main() {
403 let test_struct = dep::test_mod::TestStruct {};
404 test_struct.random_method()$0
405}
406"#,
407 );
408 }
409
410 #[test]
411 fn no_trait_type_fuzzy_completion() {
412 check(
413 r#"
414//- /lib.rs crate:dep
415pub mod test_mod {
416 pub trait TestTrait {
417 const SPECIAL_CONST: u8;
418 type HumbleType;
419 fn weird_function();
420 fn random_method(&self);
421 }
422 pub struct TestStruct {}
423 impl TestTrait for TestStruct {
424 const SPECIAL_CONST: u8 = 42;
425 type HumbleType = ();
426 fn weird_function() {}
427 fn random_method(&self) {}
428 }
429}
430
431//- /main.rs crate:main deps:dep
432fn main() {
433 dep::test_mod::TestStruct::hum$0
434}
435"#,
436 expect![[r#""#]],
437 );
438 }
439
440 #[test]
262 fn does_not_propose_names_in_scope() { 441 fn does_not_propose_names_in_scope() {
263 check( 442 check(
264 r#" 443 r#"
@@ -288,4 +467,61 @@ fn main() {
288 expect![[r#""#]], 467 expect![[r#""#]],
289 ); 468 );
290 } 469 }
470
471 #[test]
472 fn does_not_propose_traits_in_scope() {
473 check(
474 r#"
475//- /lib.rs crate:dep
476pub mod test_mod {
477 pub trait TestTrait {
478 const SPECIAL_CONST: u8;
479 type HumbleType;
480 fn weird_function();
481 fn random_method(&self);
482 }
483 pub struct TestStruct {}
484 impl TestTrait for TestStruct {
485 const SPECIAL_CONST: u8 = 42;
486 type HumbleType = ();
487 fn weird_function() {}
488 fn random_method(&self) {}
489 }
490}
491
492//- /main.rs crate:main deps:dep
493use dep::test_mod::{TestStruct, TestTrait};
494fn main() {
495 dep::test_mod::TestStruct::hum$0
496}
497"#,
498 expect![[r#""#]],
499 );
500 }
501
502 #[test]
503 fn blanket_trait_impl_import() {
504 check(
505 r#"
506//- /lib.rs crate:dep
507pub mod test_mod {
508 pub struct TestStruct {}
509 pub trait TestTrait {
510 fn another_function();
511 }
512 impl<T> TestTrait for T {
513 fn another_function() {}
514 }
515}
516
517//- /main.rs crate:main deps:dep
518fn main() {
519 dep::test_mod::TestStruct::ano$0
520}
521"#,
522 expect![[r#"
523 fn another_function() (dep::test_mod::TestTrait) fn another_function()
524 "#]],
525 );
526 }
291} 527}
diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs
index 58fc700f3..d70ed6c1c 100644
--- a/crates/completion/src/config.rs
+++ b/crates/completion/src/config.rs
@@ -9,7 +9,7 @@ use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
9#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct CompletionConfig { 10pub struct CompletionConfig {
11 pub enable_postfix_completions: bool, 11 pub enable_postfix_completions: bool,
12 pub enable_autoimport_completions: bool, 12 pub enable_imports_on_the_fly: bool,
13 pub add_call_parenthesis: bool, 13 pub add_call_parenthesis: bool,
14 pub add_call_argument_snippets: bool, 14 pub add_call_argument_snippets: bool,
15 pub snippet_cap: Option<SnippetCap>, 15 pub snippet_cap: Option<SnippetCap>,
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs
index 0134ff219..378bd2c70 100644
--- a/crates/completion/src/item.rs
+++ b/crates/completion/src/item.rs
@@ -270,6 +270,7 @@ impl CompletionItem {
270pub struct ImportEdit { 270pub struct ImportEdit {
271 pub import_path: ModPath, 271 pub import_path: ModPath,
272 pub import_scope: ImportScope, 272 pub import_scope: ImportScope,
273 pub import_for_trait_assoc_item: bool,
273} 274}
274 275
275impl ImportEdit { 276impl ImportEdit {
@@ -321,17 +322,19 @@ impl Builder {
321 let mut insert_text = self.insert_text; 322 let mut insert_text = self.insert_text;
322 323
323 if let Some(import_to_add) = self.import_to_add.as_ref() { 324 if let Some(import_to_add) = self.import_to_add.as_ref() {
324 let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); 325 if import_to_add.import_for_trait_assoc_item {
325 let _ = import_path_without_last_segment.segments.pop(); 326 lookup = lookup.or_else(|| Some(label.clone()));
326 327 insert_text = insert_text.or_else(|| Some(label.clone()));
327 if !import_path_without_last_segment.segments.is_empty() { 328 label = format!("{} ({})", label, import_to_add.import_path);
328 if lookup.is_none() { 329 } else {
329 lookup = Some(label.clone()); 330 let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
330 } 331 let _ = import_path_without_last_segment.segments.pop();
331 if insert_text.is_none() { 332
332 insert_text = Some(label.clone()); 333 if !import_path_without_last_segment.segments.is_empty() {
334 lookup = lookup.or_else(|| Some(label.clone()));
335 insert_text = insert_text.or_else(|| Some(label.clone()));
336 label = format!("{}::{}", import_path_without_last_segment, label);
333 } 337 }
334 label = format!("{}::{}", import_path_without_last_segment, label);
335 } 338 }
336 } 339 }
337 340
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index ee1b822e7..56ec13e8c 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -139,6 +139,7 @@ pub fn resolve_completion_edits(
139 position: FilePosition, 139 position: FilePosition,
140 full_import_path: &str, 140 full_import_path: &str,
141 imported_name: String, 141 imported_name: String,
142 import_for_trait_assoc_item: bool,
142) -> Option<Vec<TextEdit>> { 143) -> Option<Vec<TextEdit>> {
143 let ctx = CompletionContext::new(db, position, config)?; 144 let ctx = CompletionContext::new(db, position, config)?;
144 let anchor = ctx.name_ref_syntax.as_ref()?; 145 let anchor = ctx.name_ref_syntax.as_ref()?;
@@ -154,7 +155,7 @@ pub fn resolve_completion_edits(
154 }) 155 })
155 .find(|mod_path| mod_path.to_string() == full_import_path)?; 156 .find(|mod_path| mod_path.to_string() == full_import_path)?;
156 157
157 ImportEdit { import_path, import_scope } 158 ImportEdit { import_path, import_scope, import_for_trait_assoc_item }
158 .to_text_edit(config.insert_use.merge) 159 .to_text_edit(config.insert_use.merge)
159 .map(|edit| vec![edit]) 160 .map(|edit| vec![edit])
160} 161}
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
index 820dd01d1..4b3c9702a 100644
--- a/crates/completion/src/render.rs
+++ b/crates/completion/src/render.rs
@@ -10,7 +10,7 @@ pub(crate) mod type_alias;
10 10
11mod builder_ext; 11mod builder_ext;
12 12
13use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; 13use hir::{Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type};
14use ide_db::{helpers::SnippetCap, RootDatabase}; 14use ide_db::{helpers::SnippetCap, RootDatabase};
15use syntax::TextRange; 15use syntax::TextRange;
16use test_utils::mark; 16use test_utils::mark;
@@ -51,16 +51,16 @@ pub(crate) fn render_resolution_with_import<'a>(
51 import_edit: ImportEdit, 51 import_edit: ImportEdit,
52 resolution: &ScopeDef, 52 resolution: &ScopeDef,
53) -> Option<CompletionItem> { 53) -> Option<CompletionItem> {
54 Render::new(ctx) 54 let local_name = match resolution {
55 .render_resolution( 55 ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
56 import_edit.import_path.segments.last()?.to_string(), 56 ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(),
57 Some(import_edit), 57 ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(),
58 resolution, 58 _ => import_edit.import_path.segments.last()?.to_string(),
59 ) 59 };
60 .map(|mut item| { 60 Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| {
61 item.completion_kind = CompletionKind::Magic; 61 item.completion_kind = CompletionKind::Magic;
62 item 62 item
63 }) 63 })
64} 64}
65 65
66/// Interface for data and methods required for items rendering. 66/// Interface for data and methods required for items rendering.
diff --git a/crates/completion/src/test_utils.rs b/crates/completion/src/test_utils.rs
index 6ea6da989..3faf861b9 100644
--- a/crates/completion/src/test_utils.rs
+++ b/crates/completion/src/test_utils.rs
@@ -18,7 +18,7 @@ use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
18 18
19pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { 19pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
20 enable_postfix_completions: true, 20 enable_postfix_completions: true,
21 enable_autoimport_completions: true, 21 enable_imports_on_the_fly: true,
22 add_call_parenthesis: true, 22 add_call_parenthesis: true,
23 add_call_argument_snippets: true, 23 add_call_argument_snippets: true,
24 snippet_cap: SnippetCap::new(true), 24 snippet_cap: SnippetCap::new(true),
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 6cbf5cecf..2950f08b8 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -272,6 +272,15 @@ impl ModuleDef {
272 272
273 hir_ty::diagnostics::validate_module_item(db, module.id.krate, id, sink) 273 hir_ty::diagnostics::validate_module_item(db, module.id.krate, id, sink)
274 } 274 }
275
276 pub fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> {
277 match self {
278 ModuleDef::Function(f) => f.as_assoc_item(db),
279 ModuleDef::Const(c) => c.as_assoc_item(db),
280 ModuleDef::TypeAlias(t) => t.as_assoc_item(db),
281 _ => None,
282 }
283 }
275} 284}
276 285
277impl Module { 286impl Module {
@@ -1091,6 +1100,13 @@ impl AssocItem {
1091 AssocContainerId::ContainerId(_) => panic!("invalid AssocItem"), 1100 AssocContainerId::ContainerId(_) => panic!("invalid AssocItem"),
1092 } 1101 }
1093 } 1102 }
1103
1104 pub fn containing_trait(self, db: &dyn HirDatabase) -> Option<Trait> {
1105 match self.container(db) {
1106 AssocItemContainer::Trait(t) => Some(t),
1107 _ => None,
1108 }
1109 }
1094} 1110}
1095 1111
1096impl HasVisibility for AssocItem { 1112impl HasVisibility for AssocItem {
diff --git a/crates/hir_def/src/import_map.rs b/crates/hir_def/src/import_map.rs
index e5368b293..fac0de90c 100644
--- a/crates/hir_def/src/import_map.rs
+++ b/crates/hir_def/src/import_map.rs
@@ -263,6 +263,7 @@ pub enum ImportKind {
263 Trait, 263 Trait,
264 TypeAlias, 264 TypeAlias,
265 BuiltinType, 265 BuiltinType,
266 AssociatedItem,
266} 267}
267 268
268/// A way to match import map contents against the search query. 269/// A way to match import map contents against the search query.
@@ -282,6 +283,7 @@ pub struct Query {
282 query: String, 283 query: String,
283 lowercased: String, 284 lowercased: String,
284 name_only: bool, 285 name_only: bool,
286 assoc_items_only: bool,
285 search_mode: SearchMode, 287 search_mode: SearchMode,
286 case_sensitive: bool, 288 case_sensitive: bool,
287 limit: usize, 289 limit: usize,
@@ -295,6 +297,7 @@ impl Query {
295 query, 297 query,
296 lowercased, 298 lowercased,
297 name_only: false, 299 name_only: false,
300 assoc_items_only: false,
298 search_mode: SearchMode::Contains, 301 search_mode: SearchMode::Contains,
299 case_sensitive: false, 302 case_sensitive: false,
300 limit: usize::max_value(), 303 limit: usize::max_value(),
@@ -309,6 +312,11 @@ impl Query {
309 Self { name_only: true, ..self } 312 Self { name_only: true, ..self }
310 } 313 }
311 314
315 /// Matches only the entries that are associated items, ignoring the rest.
316 pub fn assoc_items_only(self) -> Self {
317 Self { assoc_items_only: true, ..self }
318 }
319
312 /// Specifies the way to search for the entries using the query. 320 /// Specifies the way to search for the entries using the query.
313 pub fn search_mode(self, search_mode: SearchMode) -> Self { 321 pub fn search_mode(self, search_mode: SearchMode) -> Self {
314 Self { search_mode, ..self } 322 Self { search_mode, ..self }
@@ -331,6 +339,14 @@ impl Query {
331 } 339 }
332 340
333 fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool { 341 fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool {
342 if import.is_trait_assoc_item {
343 if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) {
344 return false;
345 }
346 } else if self.assoc_items_only {
347 return false;
348 }
349
334 let mut input = if import.is_trait_assoc_item || self.name_only { 350 let mut input = if import.is_trait_assoc_item || self.name_only {
335 import.path.segments.last().unwrap().to_string() 351 import.path.segments.last().unwrap().to_string()
336 } else { 352 } else {
@@ -814,6 +830,56 @@ mod tests {
814 } 830 }
815 831
816 #[test] 832 #[test]
833 fn assoc_items_filtering() {
834 let ra_fixture = r#"
835 //- /main.rs crate:main deps:dep
836 //- /dep.rs crate:dep
837 pub mod fmt {
838 pub trait Display {
839 type FmtTypeAlias;
840 const FMT_CONST: bool;
841
842 fn format_function();
843 fn format_method(&self);
844 }
845 }
846 "#;
847
848 check_search(
849 ra_fixture,
850 "main",
851 Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy).assoc_items_only(),
852 expect![[r#"
853 dep::fmt::Display::FMT_CONST (a)
854 dep::fmt::Display::format_function (a)
855 dep::fmt::Display::format_method (a)
856 "#]],
857 );
858
859 check_search(
860 ra_fixture,
861 "main",
862 Query::new("fmt".to_string())
863 .search_mode(SearchMode::Fuzzy)
864 .exclude_import_kind(ImportKind::AssociatedItem),
865 expect![[r#"
866 dep::fmt (t)
867 dep::fmt::Display (t)
868 "#]],
869 );
870
871 check_search(
872 ra_fixture,
873 "main",
874 Query::new("fmt".to_string())
875 .search_mode(SearchMode::Fuzzy)
876 .assoc_items_only()
877 .exclude_import_kind(ImportKind::AssociatedItem),
878 expect![[r#""#]],
879 );
880 }
881
882 #[test]
817 fn search_mode() { 883 fn search_mode() {
818 let ra_fixture = r#" 884 let ra_fixture = r#"
819 //- /main.rs crate:main deps:dep 885 //- /main.rs crate:main deps:dep
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index de10406bc..1f08d7810 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -438,10 +438,10 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem)
438 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), 438 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
439 FieldOrAssocItem::AssocItem(assoc) => match assoc { 439 FieldOrAssocItem::AssocItem(assoc) => match assoc {
440 AssocItem::Function(function) => { 440 AssocItem::Function(function) => {
441 let is_trait_method = matches!( 441 let is_trait_method = function
442 function.as_assoc_item(db).map(|assoc| assoc.container(db)), 442 .as_assoc_item(db)
443 Some(AssocItemContainer::Trait(..)) 443 .and_then(|assoc| assoc.containing_trait(db))
444 ); 444 .is_some();
445 // This distinction may get more complicated when specialization is available. 445 // This distinction may get more complicated when specialization is available.
446 // Rustdoc makes this decision based on whether a method 'has defaultness'. 446 // Rustdoc makes this decision based on whether a method 'has defaultness'.
447 // Currently this is only the case for provided trait methods. 447 // Currently this is only the case for provided trait methods.
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index f8d69382e..07f52613f 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -481,6 +481,7 @@ impl Analysis {
481 position: FilePosition, 481 position: FilePosition,
482 full_import_path: &str, 482 full_import_path: &str,
483 imported_name: String, 483 imported_name: String,
484 import_for_trait_assoc_item: bool,
484 ) -> Cancelable<Vec<TextEdit>> { 485 ) -> Cancelable<Vec<TextEdit>> {
485 Ok(self 486 Ok(self
486 .with_db(|db| { 487 .with_db(|db| {
@@ -490,6 +491,7 @@ impl Analysis {
490 position, 491 position,
491 full_import_path, 492 full_import_path,
492 imported_name, 493 imported_name,
494 import_for_trait_assoc_item,
493 ) 495 )
494 })? 496 })?
495 .unwrap_or_default()) 497 .unwrap_or_default())
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index edc3da318..e284220c1 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -1,12 +1,13 @@
1//! Look up accessible paths for items. 1//! Look up accessible paths for items.
2use either::Either; 2use either::Either;
3use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; 3use hir::{AsAssocItem, AssocItem, Module, ModuleDef, PrefixKind, Semantics};
4use rustc_hash::FxHashSet; 4use rustc_hash::FxHashSet;
5use syntax::{ast, AstNode, SyntaxNode}; 5use syntax::{ast, AstNode};
6 6
7use crate::{imports_locator, RootDatabase}; 7use crate::{
8 8 imports_locator::{self, AssocItemSearch},
9use super::insert_use::InsertUseConfig; 9 RootDatabase,
10};
10 11
11#[derive(Debug)] 12#[derive(Debug)]
12pub enum ImportCandidate { 13pub enum ImportCandidate {
@@ -24,86 +25,141 @@ pub enum ImportCandidate {
24 25
25#[derive(Debug)] 26#[derive(Debug)]
26pub struct TraitImportCandidate { 27pub struct TraitImportCandidate {
27 pub ty: hir::Type, 28 pub receiver_ty: hir::Type,
28 pub name: ast::NameRef, 29 pub name: NameToImport,
29} 30}
30 31
31#[derive(Debug)] 32#[derive(Debug)]
32pub struct PathImportCandidate { 33pub struct PathImportCandidate {
33 pub qualifier: Option<ast::Path>, 34 pub qualifier: Option<ast::Path>,
34 pub name: ast::NameRef, 35 pub name: NameToImport,
36}
37
38#[derive(Debug)]
39pub enum NameToImport {
40 Exact(String),
41 Fuzzy(String),
42}
43
44impl NameToImport {
45 pub fn text(&self) -> &str {
46 match self {
47 NameToImport::Exact(text) => text.as_str(),
48 NameToImport::Fuzzy(text) => text.as_str(),
49 }
50 }
35} 51}
36 52
37#[derive(Debug)] 53#[derive(Debug)]
38pub struct ImportAssets { 54pub struct ImportAssets {
39 import_candidate: ImportCandidate, 55 import_candidate: ImportCandidate,
40 module_with_name_to_import: hir::Module, 56 module_with_candidate: hir::Module,
41 syntax_under_caret: SyntaxNode,
42} 57}
43 58
44impl ImportAssets { 59impl ImportAssets {
45 pub fn for_method_call( 60 pub fn for_method_call(
46 method_call: ast::MethodCallExpr, 61 method_call: &ast::MethodCallExpr,
47 sema: &Semantics<RootDatabase>, 62 sema: &Semantics<RootDatabase>,
48 ) -> Option<Self> { 63 ) -> Option<Self> {
49 let syntax_under_caret = method_call.syntax().to_owned();
50 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
51 Some(Self { 64 Some(Self {
52 import_candidate: ImportCandidate::for_method_call(sema, &method_call)?, 65 import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
53 module_with_name_to_import, 66 module_with_candidate: sema.scope(method_call.syntax()).module()?,
54 syntax_under_caret,
55 }) 67 })
56 } 68 }
57 69
58 pub fn for_regular_path( 70 pub fn for_exact_path(
59 path_under_caret: ast::Path, 71 fully_qualified_path: &ast::Path,
60 sema: &Semantics<RootDatabase>, 72 sema: &Semantics<RootDatabase>,
61 ) -> Option<Self> { 73 ) -> Option<Self> {
62 let syntax_under_caret = path_under_caret.syntax().to_owned(); 74 let syntax_under_caret = fully_qualified_path.syntax();
63 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { 75 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
64 return None; 76 return None;
65 } 77 }
66
67 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
68 Some(Self { 78 Some(Self {
69 import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?, 79 import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
70 module_with_name_to_import, 80 module_with_candidate: sema.scope(syntax_under_caret).module()?,
71 syntax_under_caret, 81 })
82 }
83
84 pub fn for_fuzzy_path(
85 module_with_path: Module,
86 qualifier: Option<ast::Path>,
87 fuzzy_name: String,
88 sema: &Semantics<RootDatabase>,
89 ) -> Option<Self> {
90 Some(match qualifier {
91 Some(qualifier) => {
92 let qualifier_resolution = sema.resolve_path(&qualifier)?;
93 match qualifier_resolution {
94 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => Self {
95 import_candidate: ImportCandidate::TraitAssocItem(TraitImportCandidate {
96 receiver_ty: assoc_item_path.ty(sema.db),
97 name: NameToImport::Fuzzy(fuzzy_name),
98 }),
99 module_with_candidate: module_with_path,
100 },
101 _ => Self {
102 import_candidate: ImportCandidate::Path(PathImportCandidate {
103 qualifier: Some(qualifier),
104 name: NameToImport::Fuzzy(fuzzy_name),
105 }),
106 module_with_candidate: module_with_path,
107 },
108 }
109 }
110 None => Self {
111 import_candidate: ImportCandidate::Path(PathImportCandidate {
112 qualifier: None,
113 name: NameToImport::Fuzzy(fuzzy_name),
114 }),
115 module_with_candidate: module_with_path,
116 },
72 }) 117 })
73 } 118 }
74 119
75 pub fn syntax_under_caret(&self) -> &SyntaxNode { 120 pub fn for_fuzzy_method_call(
76 &self.syntax_under_caret 121 module_with_method_call: Module,
122 receiver_ty: hir::Type,
123 fuzzy_method_name: String,
124 ) -> Option<Self> {
125 Some(Self {
126 import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
127 receiver_ty,
128 name: NameToImport::Fuzzy(fuzzy_method_name),
129 }),
130 module_with_candidate: module_with_method_call,
131 })
77 } 132 }
133}
78 134
135impl ImportAssets {
79 pub fn import_candidate(&self) -> &ImportCandidate { 136 pub fn import_candidate(&self) -> &ImportCandidate {
80 &self.import_candidate 137 &self.import_candidate
81 } 138 }
82 139
83 fn get_search_query(&self) -> &str { 140 fn name_to_import(&self) -> &NameToImport {
84 match &self.import_candidate { 141 match &self.import_candidate {
85 ImportCandidate::Path(candidate) => candidate.name.text(), 142 ImportCandidate::Path(candidate) => &candidate.name,
86 ImportCandidate::TraitAssocItem(candidate) 143 ImportCandidate::TraitAssocItem(candidate)
87 | ImportCandidate::TraitMethod(candidate) => candidate.name.text(), 144 | ImportCandidate::TraitMethod(candidate) => &candidate.name,
88 } 145 }
89 } 146 }
90 147
91 pub fn search_for_imports( 148 pub fn search_for_imports(
92 &self, 149 &self,
93 sema: &Semantics<RootDatabase>, 150 sema: &Semantics<RootDatabase>,
94 config: &InsertUseConfig, 151 prefix_kind: PrefixKind,
95 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 152 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
96 let _p = profile::span("import_assists::search_for_imports"); 153 let _p = profile::span("import_assets::search_for_imports");
97 self.search_for(sema, Some(config.prefix_kind)) 154 self.search_for(sema, Some(prefix_kind))
98 } 155 }
99 156
100 /// This may return non-absolute paths if a part of the returned path is already imported into scope. 157 /// This may return non-absolute paths if a part of the returned path is already imported into scope.
101 #[allow(dead_code)]
102 pub fn search_for_relative_paths( 158 pub fn search_for_relative_paths(
103 &self, 159 &self,
104 sema: &Semantics<RootDatabase>, 160 sema: &Semantics<RootDatabase>,
105 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 161 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
106 let _p = profile::span("import_assists::search_for_relative_paths"); 162 let _p = profile::span("import_assets::search_for_relative_paths");
107 self.search_for(sema, None) 163 self.search_for(sema, None)
108 } 164 }
109 165
@@ -114,60 +170,56 @@ impl ImportAssets {
114 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 170 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
115 let db = sema.db; 171 let db = sema.db;
116 let mut trait_candidates = FxHashSet::default(); 172 let mut trait_candidates = FxHashSet::default();
117 let current_crate = self.module_with_name_to_import.krate(); 173 let current_crate = self.module_with_candidate.krate();
118 174
119 let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| { 175 let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
120 trait_candidates.clear(); 176 trait_candidates.clear();
121 match &self.import_candidate { 177 match &self.import_candidate {
122 ImportCandidate::TraitAssocItem(trait_candidate) => { 178 ImportCandidate::TraitAssocItem(trait_candidate) => {
123 let located_assoc_item = match candidate { 179 let canidate_assoc_item = match candidate {
124 Either::Left(ModuleDef::Function(located_function)) => { 180 Either::Left(module_def) => module_def.as_assoc_item(db),
125 located_function.as_assoc_item(db)
126 }
127 Either::Left(ModuleDef::Const(located_const)) => {
128 located_const.as_assoc_item(db)
129 }
130 _ => None, 181 _ => None,
131 } 182 }?;
132 .map(|assoc| assoc.container(db)) 183 trait_candidates.insert(canidate_assoc_item.containing_trait(db)?.into());
133 .and_then(Self::assoc_to_trait)?;
134
135 trait_candidates.insert(located_assoc_item.into());
136 184
137 trait_candidate 185 trait_candidate
138 .ty 186 .receiver_ty
139 .iterate_path_candidates( 187 .iterate_path_candidates(
140 db, 188 db,
141 current_crate, 189 current_crate,
142 &trait_candidates, 190 &trait_candidates,
143 None, 191 None,
144 |_, assoc| Self::assoc_to_trait(assoc.container(db)), 192 |_, assoc| {
193 if canidate_assoc_item == assoc {
194 Some(assoc_to_module_def(assoc))
195 } else {
196 None
197 }
198 },
145 ) 199 )
146 .map(ModuleDef::from)
147 .map(Either::Left) 200 .map(Either::Left)
148 } 201 }
149 ImportCandidate::TraitMethod(trait_candidate) => { 202 ImportCandidate::TraitMethod(trait_candidate) => {
150 let located_assoc_item = 203 let canidate_assoc_item = match candidate {
151 if let Either::Left(ModuleDef::Function(located_function)) = candidate { 204 Either::Left(module_def) => module_def.as_assoc_item(db),
152 located_function 205 _ => None,
153 .as_assoc_item(db) 206 }?;
154 .map(|assoc| assoc.container(db)) 207 trait_candidates.insert(canidate_assoc_item.containing_trait(db)?.into());
155 .and_then(Self::assoc_to_trait)
156 } else {
157 None
158 }?;
159
160 trait_candidates.insert(located_assoc_item.into());
161 208
162 trait_candidate 209 trait_candidate
163 .ty 210 .receiver_ty
164 .iterate_method_candidates( 211 .iterate_method_candidates(
165 db, 212 db,
166 current_crate, 213 current_crate,
167 &trait_candidates, 214 &trait_candidates,
168 None, 215 None,
169 |_, function| { 216 |_, function| {
170 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db)) 217 let assoc = function.as_assoc_item(db)?;
218 if canidate_assoc_item == assoc {
219 Some(assoc_to_module_def(assoc))
220 } else {
221 None
222 }
171 }, 223 },
172 ) 224 )
173 .map(ModuleDef::from) 225 .map(ModuleDef::from)
@@ -177,34 +229,69 @@ impl ImportAssets {
177 } 229 }
178 }; 230 };
179 231
180 let mut res = imports_locator::find_exact_imports( 232 let unfiltered_imports = match self.name_to_import() {
181 sema, 233 NameToImport::Exact(exact_name) => {
182 current_crate, 234 imports_locator::find_exact_imports(sema, current_crate, exact_name.clone())
183 self.get_search_query().to_string(),
184 )
185 .filter_map(filter)
186 .filter_map(|candidate| {
187 let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
188 if let Some(prefix_kind) = prefixed {
189 self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
190 } else {
191 self.module_with_name_to_import.find_use_path(db, item)
192 } 235 }
193 .map(|path| (path, item)) 236 // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
194 }) 237 // instead, we need to look up all trait impls for a certain struct and search through them only
195 .filter(|(use_path, _)| use_path.len() > 1) 238 // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032
196 .take(20) 239 // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup
197 .collect::<Vec<_>>(); 240 // for the details
198 res.sort_by_key(|(path, _)| path.clone()); 241 NameToImport::Fuzzy(fuzzy_name) => imports_locator::find_similar_imports(
242 sema,
243 current_crate,
244 fuzzy_name.clone(),
245 match self.import_candidate {
246 ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
247 AssocItemSearch::AssocItemsOnly
248 }
249 _ => AssocItemSearch::Exclude,
250 },
251 ),
252 };
253
254 let mut res = unfiltered_imports
255 .filter_map(filter)
256 .filter_map(|candidate| {
257 let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into);
258
259 let item_to_search = match self.import_candidate {
260 ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
261 let canidate_trait = match candidate {
262 Either::Left(module_def) => {
263 module_def.as_assoc_item(db)?.containing_trait(db)
264 }
265 _ => None,
266 }?;
267 ModuleDef::from(canidate_trait).into()
268 }
269 _ => item,
270 };
271
272 if let Some(prefix_kind) = prefixed {
273 self.module_with_candidate.find_use_path_prefixed(
274 db,
275 item_to_search,
276 prefix_kind,
277 )
278 } else {
279 self.module_with_candidate.find_use_path(db, item_to_search)
280 }
281 .map(|path| (path, item))
282 })
283 .filter(|(use_path, _)| use_path.len() > 1)
284 .collect::<Vec<_>>();
285 res.sort_by_cached_key(|(path, _)| path.clone());
199 res 286 res
200 } 287 }
288}
201 289
202 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { 290fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef {
203 if let AssocItemContainer::Trait(extracted_trait) = assoc { 291 match assoc {
204 Some(extracted_trait) 292 AssocItem::Function(f) => f.into(),
205 } else { 293 AssocItem::Const(c) => c.into(),
206 None 294 AssocItem::TypeAlias(t) => t.into(),
207 }
208 } 295 }
209} 296}
210 297
@@ -216,22 +303,19 @@ impl ImportCandidate {
216 match sema.resolve_method_call(method_call) { 303 match sema.resolve_method_call(method_call) {
217 Some(_) => None, 304 Some(_) => None,
218 None => Some(Self::TraitMethod(TraitImportCandidate { 305 None => Some(Self::TraitMethod(TraitImportCandidate {
219 ty: sema.type_of_expr(&method_call.receiver()?)?, 306 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
220 name: method_call.name_ref()?, 307 name: NameToImport::Exact(method_call.name_ref()?.to_string()),
221 })), 308 })),
222 } 309 }
223 } 310 }
224 311
225 fn for_regular_path( 312 fn for_regular_path(sema: &Semantics<RootDatabase>, path: &ast::Path) -> Option<Self> {
226 sema: &Semantics<RootDatabase>, 313 if sema.resolve_path(path).is_some() {
227 path_under_caret: &ast::Path,
228 ) -> Option<Self> {
229 if sema.resolve_path(path_under_caret).is_some() {
230 return None; 314 return None;
231 } 315 }
232 316
233 let segment = path_under_caret.segment()?; 317 let segment = path.segment()?;
234 let candidate = if let Some(qualifier) = path_under_caret.qualifier() { 318 let candidate = if let Some(qualifier) = path.qualifier() {
235 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; 319 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
236 let qualifier_start_path = 320 let qualifier_start_path =
237 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; 321 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
@@ -244,8 +328,8 @@ impl ImportCandidate {
244 match qualifier_resolution { 328 match qualifier_resolution {
245 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { 329 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
246 ImportCandidate::TraitAssocItem(TraitImportCandidate { 330 ImportCandidate::TraitAssocItem(TraitImportCandidate {
247 ty: assoc_item_path.ty(sema.db), 331 receiver_ty: assoc_item_path.ty(sema.db),
248 name: segment.name_ref()?, 332 name: NameToImport::Exact(segment.name_ref()?.to_string()),
249 }) 333 })
250 } 334 }
251 _ => return None, 335 _ => return None,
@@ -253,13 +337,15 @@ impl ImportCandidate {
253 } else { 337 } else {
254 ImportCandidate::Path(PathImportCandidate { 338 ImportCandidate::Path(PathImportCandidate {
255 qualifier: Some(qualifier), 339 qualifier: Some(qualifier),
256 name: qualifier_start, 340 name: NameToImport::Exact(qualifier_start.to_string()),
257 }) 341 })
258 } 342 }
259 } else { 343 } else {
260 ImportCandidate::Path(PathImportCandidate { 344 ImportCandidate::Path(PathImportCandidate {
261 qualifier: None, 345 qualifier: None,
262 name: segment.syntax().descendants().find_map(ast::NameRef::cast)?, 346 name: NameToImport::Exact(
347 segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(),
348 ),
263 }) 349 })
264 }; 350 };
265 Some(candidate) 351 Some(candidate)
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs
index d111fba92..d69e65960 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/imports_locator.rs
@@ -1,7 +1,10 @@
1//! This module contains an import search functionality that is provided to the assists module. 1//! This module contains an import search functionality that is provided to the assists module.
2//! Later, this should be moved away to a separate crate that is accessible from the assists module. 2//! Later, this should be moved away to a separate crate that is accessible from the assists module.
3 3
4use hir::{import_map, AsAssocItem, Crate, MacroDef, ModuleDef, Semantics}; 4use hir::{
5 import_map::{self, ImportKind},
6 AsAssocItem, Crate, MacroDef, ModuleDef, Semantics,
7};
5use syntax::{ast, AstNode, SyntaxKind::NAME}; 8use syntax::{ast, AstNode, SyntaxKind::NAME};
6 9
7use crate::{ 10use crate::{
@@ -18,9 +21,9 @@ pub fn find_exact_imports<'a>(
18 sema: &Semantics<'a, RootDatabase>, 21 sema: &Semantics<'a, RootDatabase>,
19 krate: Crate, 22 krate: Crate,
20 name_to_import: String, 23 name_to_import: String,
21) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 24) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>>> {
22 let _p = profile::span("find_exact_imports"); 25 let _p = profile::span("find_exact_imports");
23 find_imports( 26 Box::new(find_imports(
24 sema, 27 sema,
25 krate, 28 krate,
26 { 29 {
@@ -34,47 +37,58 @@ pub fn find_exact_imports<'a>(
34 .name_only() 37 .name_only()
35 .search_mode(import_map::SearchMode::Equals) 38 .search_mode(import_map::SearchMode::Equals)
36 .case_sensitive(), 39 .case_sensitive(),
37 ) 40 ))
41}
42
43pub enum AssocItemSearch {
44 Include,
45 Exclude,
46 AssocItemsOnly,
38} 47}
39 48
40pub fn find_similar_imports<'a>( 49pub fn find_similar_imports<'a>(
41 sema: &Semantics<'a, RootDatabase>, 50 sema: &Semantics<'a, RootDatabase>,
42 krate: Crate, 51 krate: Crate,
43 limit: Option<usize>,
44 fuzzy_search_string: String, 52 fuzzy_search_string: String,
45 ignore_assoc_items: bool, 53 assoc_item_search: AssocItemSearch,
46 name_only: bool, 54) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> {
47) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> + 'a {
48 let _p = profile::span("find_similar_imports"); 55 let _p = profile::span("find_similar_imports");
49 56
50 let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) 57 let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
51 .search_mode(import_map::SearchMode::Fuzzy); 58 .search_mode(import_map::SearchMode::Fuzzy)
52 if name_only { 59 .name_only()
53 external_query = external_query.name_only(); 60 .limit(QUERY_SEARCH_LIMIT);
61
62 match assoc_item_search {
63 AssocItemSearch::Include => {}
64 AssocItemSearch::Exclude => {
65 external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem);
66 }
67 AssocItemSearch::AssocItemsOnly => {
68 external_query = external_query.assoc_items_only();
69 }
54 } 70 }
55 71
56 let mut local_query = symbol_index::Query::new(fuzzy_search_string); 72 let mut local_query = symbol_index::Query::new(fuzzy_search_string);
57 73 local_query.limit(QUERY_SEARCH_LIMIT);
58 if let Some(limit) = limit {
59 local_query.limit(limit);
60 external_query = external_query.limit(limit);
61 }
62 74
63 let db = sema.db; 75 let db = sema.db;
64 find_imports(sema, krate, local_query, external_query).filter(move |import_candidate| { 76 Box::new(find_imports(sema, krate, local_query, external_query).filter(
65 if ignore_assoc_items { 77 move |import_candidate| match assoc_item_search {
66 match import_candidate { 78 AssocItemSearch::Include => true,
67 Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_none(), 79 AssocItemSearch::Exclude => !is_assoc_item(import_candidate, db),
68 Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_none(), 80 AssocItemSearch::AssocItemsOnly => is_assoc_item(import_candidate, db),
69 Either::Left(ModuleDef::TypeAlias(type_alias)) => { 81 },
70 type_alias.as_assoc_item(db).is_none() 82 ))
71 } 83}
72 _ => true, 84
73 } 85fn is_assoc_item(import_candidate: &Either<ModuleDef, MacroDef>, db: &RootDatabase) -> bool {
74 } else { 86 match import_candidate {
75 true 87 Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_some(),
76 } 88 Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_some(),
77 }) 89 Either::Left(ModuleDef::TypeAlias(type_alias)) => type_alias.as_assoc_item(db).is_some(),
90 _ => false,
91 }
78} 92}
79 93
80fn find_imports<'a>( 94fn find_imports<'a>(
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index a02c8327f..a01b49822 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -93,7 +93,7 @@ impl BenchCmd {
93 if is_completion { 93 if is_completion {
94 let options = CompletionConfig { 94 let options = CompletionConfig {
95 enable_postfix_completions: true, 95 enable_postfix_completions: true,
96 enable_autoimport_completions: true, 96 enable_imports_on_the_fly: true,
97 add_call_parenthesis: true, 97 add_call_parenthesis: true,
98 add_call_argument_snippets: true, 98 add_call_argument_snippets: true,
99 snippet_cap: SnippetCap::new(true), 99 snippet_cap: SnippetCap::new(true),
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index ce9655818..3ddb9e19a 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -559,7 +559,7 @@ impl Config {
559 pub fn completion(&self) -> CompletionConfig { 559 pub fn completion(&self) -> CompletionConfig {
560 CompletionConfig { 560 CompletionConfig {
561 enable_postfix_completions: self.data.completion_postfix_enable, 561 enable_postfix_completions: self.data.completion_postfix_enable,
562 enable_autoimport_completions: self.data.completion_autoimport_enable 562 enable_imports_on_the_fly: self.data.completion_autoimport_enable
563 && completion_item_edit_resolve(&self.caps), 563 && completion_item_edit_resolve(&self.caps),
564 add_call_parenthesis: self.data.completion_addCallParenthesis, 564 add_call_parenthesis: self.data.completion_addCallParenthesis,
565 add_call_argument_snippets: self.data.completion_addCallArgumentSnippets, 565 add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
@@ -581,18 +581,7 @@ impl Config {
581 AssistConfig { 581 AssistConfig {
582 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), 582 snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
583 allowed: None, 583 allowed: None,
584 insert_use: InsertUseConfig { 584 insert_use: self.insert_use_config(),
585 merge: match self.data.assist_importMergeBehavior {
586 MergeBehaviorDef::None => None,
587 MergeBehaviorDef::Full => Some(MergeBehavior::Full),
588 MergeBehaviorDef::Last => Some(MergeBehavior::Last),
589 },
590 prefix_kind: match self.data.assist_importPrefix {
591 ImportPrefixDef::Plain => PrefixKind::Plain,
592 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
593 ImportPrefixDef::BySelf => PrefixKind::BySelf,
594 },
595 },
596 } 585 }
597 } 586 }
598 pub fn call_info_full(&self) -> bool { 587 pub fn call_info_full(&self) -> bool {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 1a4e0dd32..a19e9e7dc 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -653,7 +653,7 @@ pub(crate) fn handle_completion(
653 let mut new_completion_items = 653 let mut new_completion_items =
654 to_proto::completion_item(&line_index, line_endings, item.clone()); 654 to_proto::completion_item(&line_index, line_endings, item.clone());
655 655
656 if completion_config.enable_autoimport_completions { 656 if completion_config.enable_imports_on_the_fly {
657 for new_item in &mut new_completion_items { 657 for new_item in &mut new_completion_items {
658 fill_resolve_data(&mut new_item.data, &item, &text_document_position); 658 fill_resolve_data(&mut new_item.data, &item, &text_document_position);
659 } 659 }
@@ -703,6 +703,7 @@ pub(crate) fn handle_completion_resolve(
703 FilePosition { file_id, offset }, 703 FilePosition { file_id, offset },
704 &resolve_data.full_import_path, 704 &resolve_data.full_import_path,
705 resolve_data.imported_name, 705 resolve_data.imported_name,
706 resolve_data.import_for_trait_assoc_item,
706 )? 707 )?
707 .into_iter() 708 .into_iter()
708 .flat_map(|edit| { 709 .flat_map(|edit| {
@@ -1694,6 +1695,7 @@ struct CompletionResolveData {
1694 position: lsp_types::TextDocumentPositionParams, 1695 position: lsp_types::TextDocumentPositionParams,
1695 full_import_path: String, 1696 full_import_path: String,
1696 imported_name: String, 1697 imported_name: String,
1698 import_for_trait_assoc_item: bool,
1697} 1699}
1698 1700
1699fn fill_resolve_data( 1701fn fill_resolve_data(
@@ -1710,6 +1712,7 @@ fn fill_resolve_data(
1710 position: position.to_owned(), 1712 position: position.to_owned(),
1711 full_import_path, 1713 full_import_path,
1712 imported_name, 1714 imported_name,
1715 import_for_trait_assoc_item: import_edit.import_for_trait_assoc_item,
1713 }) 1716 })
1714 .unwrap(), 1717 .unwrap(),
1715 ); 1718 );
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 1ff2d3fea..0e3550002 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -884,7 +884,7 @@ mod tests {
884 .completions( 884 .completions(
885 &ide::CompletionConfig { 885 &ide::CompletionConfig {
886 enable_postfix_completions: true, 886 enable_postfix_completions: true,
887 enable_autoimport_completions: true, 887 enable_imports_on_the_fly: true,
888 add_call_parenthesis: true, 888 add_call_parenthesis: true,
889 add_call_argument_snippets: true, 889 add_call_argument_snippets: true,
890 snippet_cap: SnippetCap::new(true), 890 snippet_cap: SnippetCap::new(true),