aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/hir/src/lib.rs11
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs52
-rw-r--r--crates/ide_assists/src/handlers/qualify_path.rs40
-rw-r--r--crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs33
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs270
-rw-r--r--crates/ide_completion/src/item.rs43
-rw-r--r--crates/ide_completion/src/lib.rs28
-rw-r--r--crates/ide_completion/src/render.rs19
-rw-r--r--crates/ide_db/src/helpers.rs10
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs578
-rw-r--r--crates/ide_db/src/items_locator.rs (renamed from crates/ide_db/src/imports_locator.rs)79
-rw-r--r--crates/ide_db/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs38
-rw-r--r--crates/rust-analyzer/src/handlers.rs10
-rw-r--r--crates/syntax/src/ast/make.rs4
-rw-r--r--crates/syntax/src/lib.rs1
-rw-r--r--crates/syntax/src/utils.rs43
18 files changed, 867 insertions, 396 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index e3a332d30..d5a3d9034 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1114,6 +1114,7 @@ pub enum AssocItem {
1114 Const(Const), 1114 Const(Const),
1115 TypeAlias(TypeAlias), 1115 TypeAlias(TypeAlias),
1116} 1116}
1117#[derive(Debug)]
1117pub enum AssocItemContainer { 1118pub enum AssocItemContainer {
1118 Trait(Trait), 1119 Trait(Trait),
1119 Impl(Impl), 1120 Impl(Impl),
@@ -2136,6 +2137,16 @@ impl ScopeDef {
2136 } 2137 }
2137} 2138}
2138 2139
2140impl From<ItemInNs> for ScopeDef {
2141 fn from(item: ItemInNs) -> Self {
2142 match item {
2143 ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
2144 ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
2145 ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
2146 }
2147 }
2148}
2149
2139pub trait HasVisibility { 2150pub trait HasVisibility {
2140 fn visibility(&self, db: &dyn HirDatabase) -> Visibility; 2151 fn visibility(&self, db: &dyn HirDatabase) -> Visibility;
2141 fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool { 2152 fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool {
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index b600178ee..f83ed65d5 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -478,7 +478,6 @@ impl Analysis {
478 position: FilePosition, 478 position: FilePosition,
479 full_import_path: &str, 479 full_import_path: &str,
480 imported_name: String, 480 imported_name: String,
481 import_for_trait_assoc_item: bool,
482 ) -> Cancelable<Vec<TextEdit>> { 481 ) -> Cancelable<Vec<TextEdit>> {
483 Ok(self 482 Ok(self
484 .with_db(|db| { 483 .with_db(|db| {
@@ -488,7 +487,6 @@ impl Analysis {
488 position, 487 position,
489 full_import_path, 488 full_import_path,
490 imported_name, 489 imported_name,
491 import_for_trait_assoc_item,
492 ) 490 )
493 })? 491 })?
494 .unwrap_or_default()) 492 .unwrap_or_default())
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index 1422224ac..7caee8df0 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -1,7 +1,7 @@
1use ide_db::helpers::{ 1use ide_db::helpers::{
2 import_assets::{ImportAssets, ImportCandidate}, 2 import_assets::{ImportAssets, ImportCandidate},
3 insert_use::{insert_use, ImportScope}, 3 insert_use::{insert_use, ImportScope},
4 mod_path_to_ast, 4 item_name, mod_path_to_ast,
5}; 5};
6use syntax::{ast, AstNode, SyntaxNode}; 6use syntax::{ast, AstNode, SyntaxNode};
7 7
@@ -92,14 +92,19 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
92 let range = ctx.sema.original_range(&syntax_under_caret).range; 92 let range = ctx.sema.original_range(&syntax_under_caret).range;
93 let group = import_group_message(import_assets.import_candidate()); 93 let group = import_group_message(import_assets.import_candidate());
94 let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?; 94 let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
95 for (import, _) in proposed_imports { 95 for import in proposed_imports {
96 let name = match item_name(ctx.db(), import.original_item) {
97 Some(name) => name,
98 None => continue,
99 };
96 acc.add_group( 100 acc.add_group(
97 &group, 101 &group,
98 AssistId("auto_import", AssistKind::QuickFix), 102 AssistId("auto_import", AssistKind::QuickFix),
99 format!("Import `{}`", &import), 103 format!("Import `{}`", name),
100 range, 104 range,
101 |builder| { 105 |builder| {
102 let rewriter = insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use); 106 let rewriter =
107 insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
103 builder.rewrite(rewriter); 108 builder.rewrite(rewriter);
104 }, 109 },
105 ); 110 );
@@ -125,10 +130,10 @@ fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
125 let name = match import_candidate { 130 let name = match import_candidate {
126 ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()), 131 ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
127 ImportCandidate::TraitAssocItem(candidate) => { 132 ImportCandidate::TraitAssocItem(candidate) => {
128 format!("Import a trait for item {}", candidate.name.text()) 133 format!("Import a trait for item {}", candidate.assoc_item_name.text())
129 } 134 }
130 ImportCandidate::TraitMethod(candidate) => { 135 ImportCandidate::TraitMethod(candidate) => {
131 format!("Import a trait for method {}", candidate.name.text()) 136 format!("Import a trait for method {}", candidate.assoc_item_name.text())
132 } 137 }
133 }; 138 };
134 GroupLabel(name) 139 GroupLabel(name)
@@ -221,41 +226,6 @@ mod tests {
221 } 226 }
222 227
223 #[test] 228 #[test]
224 fn auto_imports_are_merged() {
225 check_assist(
226 auto_import,
227 r"
228 use PubMod::PubStruct1;
229
230 struct Test {
231 test: Pub$0Struct2<u8>,
232 }
233
234 pub mod PubMod {
235 pub struct PubStruct1;
236 pub struct PubStruct2<T> {
237 _t: T,
238 }
239 }
240 ",
241 r"
242 use PubMod::{PubStruct1, PubStruct2};
243
244 struct Test {
245 test: PubStruct2<u8>,
246 }
247
248 pub mod PubMod {
249 pub struct PubStruct1;
250 pub struct PubStruct2<T> {
251 _t: T,
252 }
253 }
254 ",
255 );
256 }
257
258 #[test]
259 fn applicable_when_found_multiple_imports() { 229 fn applicable_when_found_multiple_imports() {
260 check_assist( 230 check_assist(
261 auto_import, 231 auto_import,
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs
index d3e34e540..272874ae3 100644
--- a/crates/ide_assists/src/handlers/qualify_path.rs
+++ b/crates/ide_assists/src/handlers/qualify_path.rs
@@ -1,7 +1,10 @@
1use std::iter; 1use std::iter;
2 2
3use hir::AsAssocItem; 3use hir::AsAssocItem;
4use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast}; 4use ide_db::helpers::{
5 import_assets::{ImportCandidate, LocatedImport},
6 item_name, mod_path_to_ast,
7};
5use ide_db::RootDatabase; 8use ide_db::RootDatabase;
6use syntax::{ 9use syntax::{
7 ast, 10 ast,
@@ -71,17 +74,17 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
71 }; 74 };
72 75
73 let group_label = group_label(candidate); 76 let group_label = group_label(candidate);
74 for (import, item) in proposed_imports { 77 for import in proposed_imports {
75 acc.add_group( 78 acc.add_group(
76 &group_label, 79 &group_label,
77 AssistId("qualify_path", AssistKind::QuickFix), 80 AssistId("qualify_path", AssistKind::QuickFix),
78 label(candidate, &import), 81 label(ctx.db(), candidate, &import),
79 range, 82 range,
80 |builder| { 83 |builder| {
81 qualify_candidate.qualify( 84 qualify_candidate.qualify(
82 |replace_with: String| builder.replace(range, replace_with), 85 |replace_with: String| builder.replace(range, replace_with),
83 import, 86 &import.import_path,
84 item, 87 import.item_to_import,
85 ) 88 )
86 }, 89 },
87 ); 90 );
@@ -97,8 +100,13 @@ enum QualifyCandidate<'db> {
97} 100}
98 101
99impl QualifyCandidate<'_> { 102impl QualifyCandidate<'_> {
100 fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) { 103 fn qualify(
101 let import = mod_path_to_ast(&import); 104 &self,
105 mut replacer: impl FnMut(String),
106 import: &hir::ModPath,
107 item: hir::ItemInNs,
108 ) {
109 let import = mod_path_to_ast(import);
102 match self { 110 match self {
103 QualifyCandidate::QualifierStart(segment, generics) => { 111 QualifyCandidate::QualifierStart(segment, generics) => {
104 let generics = generics.as_ref().map_or_else(String::new, ToString::to_string); 112 let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
@@ -183,23 +191,29 @@ fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
183fn group_label(candidate: &ImportCandidate) -> GroupLabel { 191fn group_label(candidate: &ImportCandidate) -> GroupLabel {
184 let name = match candidate { 192 let name = match candidate {
185 ImportCandidate::Path(it) => &it.name, 193 ImportCandidate::Path(it) => &it.name,
186 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, 194 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => {
195 &it.assoc_item_name
196 }
187 } 197 }
188 .text(); 198 .text();
189 GroupLabel(format!("Qualify {}", name)) 199 GroupLabel(format!("Qualify {}", name))
190} 200}
191 201
192fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { 202fn label(db: &RootDatabase, candidate: &ImportCandidate, import: &LocatedImport) -> String {
203 let display_path = match item_name(db, import.original_item) {
204 Some(display_path) => display_path.to_string(),
205 None => "{unknown}".to_string(),
206 };
193 match candidate { 207 match candidate {
194 ImportCandidate::Path(candidate) => { 208 ImportCandidate::Path(candidate) => {
195 if candidate.qualifier.is_some() { 209 if candidate.qualifier.is_some() {
196 format!("Qualify with `{}`", &import) 210 format!("Qualify with `{}`", display_path)
197 } else { 211 } else {
198 format!("Qualify as `{}`", &import) 212 format!("Qualify as `{}`", display_path)
199 } 213 }
200 } 214 }
201 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), 215 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", display_path),
202 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), 216 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", display_path),
203 } 217 }
204} 218}
205 219
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
index c69bc5cac..88fe2fe90 100644
--- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -1,5 +1,6 @@
1use hir::ModuleDef;
1use ide_db::helpers::mod_path_to_ast; 2use ide_db::helpers::mod_path_to_ast;
2use ide_db::imports_locator; 3use ide_db::items_locator;
3use itertools::Itertools; 4use itertools::Itertools;
4use syntax::{ 5use syntax::{
5 ast::{self, make, AstNode, NameOwner}, 6 ast::{self, make, AstNode, NameOwner},
@@ -64,22 +65,20 @@ pub(crate) fn replace_derive_with_manual_impl(
64 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; 65 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
65 let current_crate = current_module.krate(); 66 let current_crate = current_module.krate();
66 67
67 let found_traits = imports_locator::find_exact_imports( 68 let found_traits =
68 &ctx.sema, 69 items_locator::with_exact_name(&ctx.sema, current_crate, trait_token.text().to_string())
69 current_crate, 70 .into_iter()
70 trait_token.text().to_string(), 71 .filter_map(|item| match ModuleDef::from(item.as_module_def_id()?) {
71 ) 72 ModuleDef::Trait(trait_) => Some(trait_),
72 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate { 73 _ => None,
73 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_), 74 })
74 _ => None, 75 .flat_map(|trait_| {
75 }) 76 current_module
76 .flat_map(|trait_| { 77 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
77 current_module 78 .as_ref()
78 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) 79 .map(mod_path_to_ast)
79 .as_ref() 80 .zip(Some(trait_))
80 .map(mod_path_to_ast) 81 });
81 .zip(Some(trait_))
82 });
83 82
84 let mut no_traits_found = true; 83 let mut no_traits_found = true;
85 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { 84 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index f34764b61..391a11c91 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -21,6 +21,46 @@
21//! ``` 21//! ```
22//! 22//!
23//! Also completes associated items, that require trait imports. 23//! Also completes associated items, that require trait imports.
24//! If any unresolved and/or partially-qualified path predeces the input, it will be taken into account.
25//! Currently, only the imports with their import path ending with the whole qialifier will be proposed
26//! (no fuzzy matching for qualifier).
27//!
28//! ```
29//! mod foo {
30//! pub mod bar {
31//! pub struct Item;
32//!
33//! impl Item {
34//! pub const TEST_ASSOC: usize = 3;
35//! }
36//! }
37//! }
38//!
39//! fn main() {
40//! bar::Item::TEST_A$0
41//! }
42//! ```
43//! ->
44//! ```
45//! use foo::bar;
46//!
47//! mod foo {
48//! pub mod bar {
49//! pub struct Item;
50//!
51//! impl Item {
52//! pub const TEST_ASSOC: usize = 3;
53//! }
54//! }
55//! }
56//!
57//! fn main() {
58//! bar::Item::TEST_ASSOC
59//! }
60//! ```
61//!
62//! NOTE: currently, if an assoc item comes from a trait that's not currently imported and it also has an unresolved and/or partially-qualified path,
63//! no imports will be proposed.
24//! 64//!
25//! .Fuzzy search details 65//! .Fuzzy search details
26//! 66//!
@@ -48,12 +88,12 @@
48//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding 88//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
49//! capability enabled. 89//! capability enabled.
50 90
51use hir::{AsAssocItem, ModPath, ScopeDef}; 91use hir::ModPath;
52use ide_db::helpers::{ 92use ide_db::helpers::{
53 import_assets::{ImportAssets, ImportCandidate}, 93 import_assets::{ImportAssets, ImportCandidate},
54 insert_use::ImportScope, 94 insert_use::ImportScope,
55}; 95};
56use rustc_hash::FxHashSet; 96use itertools::Itertools;
57use syntax::{AstNode, SyntaxNode, T}; 97use syntax::{AstNode, SyntaxNode, T};
58 98
59use crate::{ 99use crate::{
@@ -92,50 +132,26 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
92 &ctx.sema, 132 &ctx.sema,
93 )?; 133 )?;
94 134
95 let scope_definitions = scope_definitions(ctx); 135 acc.add_all(
96 let mut all_mod_paths = import_assets 136 import_assets
97 .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) 137 .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
98 .into_iter() 138 .into_iter()
99 .map(|(mod_path, item_in_ns)| { 139 .sorted_by_key(|located_import| {
100 let scope_item = match item_in_ns { 140 compute_fuzzy_completion_order_key(
101 hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()), 141 &located_import.import_path,
102 hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()), 142 &user_input_lowercased,
103 hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()), 143 )
104 }; 144 })
105 (mod_path, scope_item) 145 .filter_map(|import| {
106 }) 146 render_resolution_with_import(
107 .filter(|(_, proposed_def)| !scope_definitions.contains(proposed_def)) 147 RenderContext::new(ctx),
108 .collect::<Vec<_>>(); 148 ImportEdit { import, scope: import_scope.clone() },
109 all_mod_paths.sort_by_cached_key(|(mod_path, _)| { 149 )
110 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) 150 }),
111 }); 151 );
112
113 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
114 let import_for_trait_assoc_item = match definition {
115 ScopeDef::ModuleDef(module_def) => module_def
116 .as_assoc_item(ctx.db)
117 .and_then(|assoc| assoc.containing_trait(ctx.db))
118 .is_some(),
119 _ => false,
120 };
121 let import_edit = ImportEdit {
122 import_path,
123 import_scope: import_scope.clone(),
124 import_for_trait_assoc_item,
125 };
126 render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
127 }));
128 Some(()) 152 Some(())
129} 153}
130 154
131fn scope_definitions(ctx: &CompletionContext) -> FxHashSet<ScopeDef> {
132 let mut scope_definitions = FxHashSet::default();
133 ctx.scope.process_all_names(&mut |_, scope_def| {
134 scope_definitions.insert(scope_def);
135 });
136 scope_definitions
137}
138
139pub(crate) fn position_for_import<'a>( 155pub(crate) fn position_for_import<'a>(
140 ctx: &'a CompletionContext, 156 ctx: &'a CompletionContext,
141 import_candidate: Option<&ImportCandidate>, 157 import_candidate: Option<&ImportCandidate>,
@@ -160,23 +176,30 @@ fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAs
160 current_module, 176 current_module,
161 ctx.sema.type_of_expr(dot_receiver)?, 177 ctx.sema.type_of_expr(dot_receiver)?,
162 fuzzy_name, 178 fuzzy_name,
179 dot_receiver.syntax().clone(),
163 ) 180 )
164 } else { 181 } else {
165 let fuzzy_name_length = fuzzy_name.len(); 182 let fuzzy_name_length = fuzzy_name.len();
183 let approximate_node = match current_module.definition_source(ctx.db).value {
184 hir::ModuleSource::SourceFile(s) => s.syntax().clone(),
185 hir::ModuleSource::Module(m) => m.syntax().clone(),
186 hir::ModuleSource::BlockExpr(b) => b.syntax().clone(),
187 };
166 let assets_for_path = ImportAssets::for_fuzzy_path( 188 let assets_for_path = ImportAssets::for_fuzzy_path(
167 current_module, 189 current_module,
168 ctx.path_qual.clone(), 190 ctx.path_qual.clone(),
169 fuzzy_name, 191 fuzzy_name,
170 &ctx.sema, 192 &ctx.sema,
171 ); 193 approximate_node,
194 )?;
172 195
173 if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_)) 196 if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_))
174 && fuzzy_name_length < 2 197 && fuzzy_name_length < 2
175 { 198 {
176 cov_mark::hit!(ignore_short_input_for_path); 199 cov_mark::hit!(ignore_short_input_for_path);
177 None 200 None
178 } else { 201 } else {
179 assets_for_path 202 Some(assets_for_path)
180 } 203 }
181 } 204 }
182} 205}
@@ -186,11 +209,11 @@ fn compute_fuzzy_completion_order_key(
186 user_input_lowercased: &str, 209 user_input_lowercased: &str,
187) -> usize { 210) -> usize {
188 cov_mark::hit!(certain_fuzzy_order_test); 211 cov_mark::hit!(certain_fuzzy_order_test);
189 let proposed_import_name = match proposed_mod_path.segments().last() { 212 let import_name = match proposed_mod_path.segments().last() {
190 Some(name) => name.to_string().to_lowercase(), 213 Some(name) => name.to_string().to_lowercase(),
191 None => return usize::MAX, 214 None => return usize::MAX,
192 }; 215 };
193 match proposed_import_name.match_indices(user_input_lowercased).next() { 216 match import_name.match_indices(user_input_lowercased).next() {
194 Some((first_matching_index, _)) => first_matching_index, 217 Some((first_matching_index, _)) => first_matching_index,
195 None => usize::MAX, 218 None => usize::MAX,
196 } 219 }
@@ -773,4 +796,155 @@ fn main() {
773}"#, 796}"#,
774 ); 797 );
775 } 798 }
799
800 #[test]
801 fn unresolved_qualifier() {
802 let fixture = r#"
803mod foo {
804 pub mod bar {
805 pub mod baz {
806 pub struct Item;
807 }
808 }
809}
810
811fn main() {
812 bar::baz::Ite$0
813}"#;
814
815 check(
816 fixture,
817 expect![[r#"
818 st foo::bar::baz::Item
819 "#]],
820 );
821
822 check_edit(
823 "Item",
824 fixture,
825 r#"
826 use foo::bar;
827
828 mod foo {
829 pub mod bar {
830 pub mod baz {
831 pub struct Item;
832 }
833 }
834 }
835
836 fn main() {
837 bar::baz::Item
838 }"#,
839 );
840 }
841
842 #[test]
843 fn unresolved_assoc_item_container() {
844 let fixture = r#"
845mod foo {
846 pub struct Item;
847
848 impl Item {
849 pub const TEST_ASSOC: usize = 3;
850 }
851}
852
853fn main() {
854 Item::TEST_A$0
855}"#;
856
857 check(
858 fixture,
859 expect![[r#"
860 ct TEST_ASSOC (foo::Item)
861 "#]],
862 );
863
864 check_edit(
865 "TEST_ASSOC",
866 fixture,
867 r#"
868use foo::Item;
869
870mod foo {
871 pub struct Item;
872
873 impl Item {
874 pub const TEST_ASSOC: usize = 3;
875 }
876}
877
878fn main() {
879 Item::TEST_ASSOC
880}"#,
881 );
882 }
883
884 #[test]
885 fn unresolved_assoc_item_container_with_path() {
886 let fixture = r#"
887mod foo {
888 pub mod bar {
889 pub struct Item;
890
891 impl Item {
892 pub const TEST_ASSOC: usize = 3;
893 }
894 }
895}
896
897fn main() {
898 bar::Item::TEST_A$0
899}"#;
900
901 check(
902 fixture,
903 expect![[r#"
904 ct TEST_ASSOC (foo::bar::Item)
905 "#]],
906 );
907
908 check_edit(
909 "TEST_ASSOC",
910 fixture,
911 r#"
912use foo::bar;
913
914mod foo {
915 pub mod bar {
916 pub struct Item;
917
918 impl Item {
919 pub const TEST_ASSOC: usize = 3;
920 }
921 }
922}
923
924fn main() {
925 bar::Item::TEST_ASSOC
926}"#,
927 );
928 }
929
930 #[test]
931 fn fuzzy_unresolved_path() {
932 check(
933 r#"
934mod foo {
935 pub mod bar {
936 pub struct Item;
937
938 impl Item {
939 pub const TEST_ASSOC: usize = 3;
940 }
941 }
942}
943
944fn main() {
945 bar::Ass$0
946}"#,
947 expect![[]],
948 )
949 }
776} 950}
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index 9b2435c4b..9b039e3e5 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -2,15 +2,16 @@
2 2
3use std::fmt; 3use std::fmt;
4 4
5use hir::{Documentation, ModPath, Mutability}; 5use hir::{Documentation, Mutability};
6use ide_db::{ 6use ide_db::{
7 helpers::{ 7 helpers::{
8 import_assets::LocatedImport,
8 insert_use::{self, ImportScope, InsertUseConfig}, 9 insert_use::{self, ImportScope, InsertUseConfig},
9 mod_path_to_ast, SnippetCap, 10 mod_path_to_ast, SnippetCap,
10 }, 11 },
11 SymbolKind, 12 SymbolKind,
12}; 13};
13use stdx::{impl_from, never}; 14use stdx::{format_to, impl_from, never};
14use syntax::{algo, TextRange}; 15use syntax::{algo, TextRange};
15use text_edit::TextEdit; 16use text_edit::TextEdit;
16 17
@@ -272,9 +273,8 @@ impl CompletionItem {
272/// An extra import to add after the completion is applied. 273/// An extra import to add after the completion is applied.
273#[derive(Debug, Clone)] 274#[derive(Debug, Clone)]
274pub struct ImportEdit { 275pub struct ImportEdit {
275 pub import_path: ModPath, 276 pub import: LocatedImport,
276 pub import_scope: ImportScope, 277 pub scope: ImportScope,
277 pub import_for_trait_assoc_item: bool,
278} 278}
279 279
280impl ImportEdit { 280impl ImportEdit {
@@ -284,7 +284,7 @@ impl ImportEdit {
284 let _p = profile::span("ImportEdit::to_text_edit"); 284 let _p = profile::span("ImportEdit::to_text_edit");
285 285
286 let rewriter = 286 let rewriter =
287 insert_use::insert_use(&self.import_scope, mod_path_to_ast(&self.import_path), cfg); 287 insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg);
288 let old_ast = rewriter.rewrite_root()?; 288 let old_ast = rewriter.rewrite_root()?;
289 let mut import_insert = TextEdit::builder(); 289 let mut import_insert = TextEdit::builder();
290 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); 290 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
@@ -322,20 +322,19 @@ impl Builder {
322 let mut lookup = self.lookup; 322 let mut lookup = self.lookup;
323 let mut insert_text = self.insert_text; 323 let mut insert_text = self.insert_text;
324 324
325 if let Some(import_to_add) = self.import_to_add.as_ref() { 325 if let Some(original_path) = self
326 if import_to_add.import_for_trait_assoc_item { 326 .import_to_add
327 lookup = lookup.or_else(|| Some(label.clone())); 327 .as_ref()
328 insert_text = insert_text.or_else(|| Some(label.clone())); 328 .and_then(|import_edit| import_edit.import.original_path.as_ref())
329 label = format!("{} ({})", label, import_to_add.import_path); 329 {
330 lookup = lookup.or_else(|| Some(label.clone()));
331 insert_text = insert_text.or_else(|| Some(label.clone()));
332
333 let original_path_label = original_path.to_string();
334 if original_path_label.ends_with(&label) {
335 label = original_path_label;
330 } else { 336 } else {
331 let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); 337 format_to!(label, " ({})", original_path)
332 let _ = import_path_without_last_segment.pop_segment();
333
334 if !import_path_without_last_segment.segments().is_empty() {
335 lookup = lookup.or_else(|| Some(label.clone()));
336 insert_text = insert_text.or_else(|| Some(label.clone()));
337 label = format!("{}::{}", import_path_without_last_segment, label);
338 }
339 } 338 }
340 } 339 }
341 340
@@ -439,9 +438,3 @@ impl Builder {
439 self 438 self
440 } 439 }
441} 440}
442
443impl<'a> Into<CompletionItem> for Builder {
444 fn into(self) -> CompletionItem {
445 self.build()
446 }
447}
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index b0b809791..a0c8c374d 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -13,7 +13,9 @@ mod completions;
13 13
14use completions::flyimport::position_for_import; 14use completions::flyimport::position_for_import;
15use ide_db::{ 15use ide_db::{
16 base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase, 16 base_db::FilePosition,
17 helpers::{import_assets::LocatedImport, insert_use::ImportScope},
18 items_locator, RootDatabase,
17}; 19};
18use text_edit::TextEdit; 20use text_edit::TextEdit;
19 21
@@ -139,25 +141,27 @@ pub fn resolve_completion_edits(
139 position: FilePosition, 141 position: FilePosition,
140 full_import_path: &str, 142 full_import_path: &str,
141 imported_name: String, 143 imported_name: String,
142 import_for_trait_assoc_item: bool,
143) -> Option<Vec<TextEdit>> { 144) -> Option<Vec<TextEdit>> {
144 let ctx = CompletionContext::new(db, position, config)?; 145 let ctx = CompletionContext::new(db, position, config)?;
145 let position_for_import = position_for_import(&ctx, None)?; 146 let position_for_import = position_for_import(&ctx, None)?;
146 let import_scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?; 147 let scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?;
147 148
148 let current_module = ctx.sema.scope(position_for_import).module()?; 149 let current_module = ctx.sema.scope(position_for_import).module()?;
149 let current_crate = current_module.krate(); 150 let current_crate = current_module.krate();
150 151
151 let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name) 152 let (import_path, item_to_import) =
152 .filter_map(|candidate| { 153 items_locator::with_exact_name(&ctx.sema, current_crate, imported_name)
153 let item: hir::ItemInNs = candidate.either(Into::into, Into::into); 154 .into_iter()
154 current_module.find_use_path_prefixed(db, item, config.insert_use.prefix_kind) 155 .filter_map(|candidate| {
155 }) 156 current_module
156 .find(|mod_path| mod_path.to_string() == full_import_path)?; 157 .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
158 .zip(Some(candidate))
159 })
160 .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?;
161 let import =
162 LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path));
157 163
158 ImportEdit { import_path, import_scope, import_for_trait_assoc_item } 164 ImportEdit { import, scope }.to_text_edit(config.insert_use).map(|edit| vec![edit])
159 .to_text_edit(config.insert_use)
160 .map(|edit| vec![edit])
161} 165}
162 166
163#[cfg(test)] 167#[cfg(test)]
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs
index dcfac23c5..fae5685e2 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -13,7 +13,10 @@ mod builder_ext;
13use hir::{ 13use hir::{
14 AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, 14 AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type,
15}; 15};
16use ide_db::{helpers::SnippetCap, RootDatabase, SymbolKind}; 16use ide_db::{
17 helpers::{item_name, SnippetCap},
18 RootDatabase, SymbolKind,
19};
17use syntax::TextRange; 20use syntax::TextRange;
18 21
19use crate::{ 22use crate::{
@@ -50,18 +53,20 @@ pub(crate) fn render_resolution<'a>(
50pub(crate) fn render_resolution_with_import<'a>( 53pub(crate) fn render_resolution_with_import<'a>(
51 ctx: RenderContext<'a>, 54 ctx: RenderContext<'a>,
52 import_edit: ImportEdit, 55 import_edit: ImportEdit,
53 resolution: &ScopeDef,
54) -> Option<CompletionItem> { 56) -> Option<CompletionItem> {
57 let resolution = ScopeDef::from(import_edit.import.original_item);
55 let local_name = match resolution { 58 let local_name = match resolution {
56 ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(), 59 ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(),
57 ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(), 60 ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(),
58 ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(), 61 ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(),
59 _ => import_edit.import_path.segments().last()?.to_string(), 62 _ => item_name(ctx.db(), import_edit.import.original_item)?.to_string(),
60 }; 63 };
61 Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| { 64 Render::new(ctx).render_resolution(local_name, Some(import_edit), &resolution).map(
62 item.completion_kind = CompletionKind::Magic; 65 |mut item| {
63 item 66 item.completion_kind = CompletionKind::Magic;
64 }) 67 item
68 },
69 )
65} 70}
66 71
67/// Interface for data and methods required for items rendering. 72/// Interface for data and methods required for items rendering.
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 3ff77400b..3c95d3cff 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -2,11 +2,19 @@
2pub mod insert_use; 2pub mod insert_use;
3pub mod import_assets; 3pub mod import_assets;
4 4
5use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; 5use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait};
6use syntax::ast::{self, make}; 6use syntax::ast::{self, make};
7 7
8use crate::RootDatabase; 8use crate::RootDatabase;
9 9
10pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
11 match item {
12 ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).name(db),
13 ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).name(db),
14 ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).name(db),
15 }
16}
17
10/// Converts the mod path struct into its ast representation. 18/// Converts the mod path struct into its ast representation.
11pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { 19pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
12 let _p = profile::span("mod_path_to_ast"); 20 let _p = profile::span("mod_path_to_ast");
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index 517abbb4b..e03ccd351 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -1,19 +1,29 @@
1//! Look up accessible paths for items. 1//! Look up accessible paths for items.
2use either::Either; 2use hir::{
3use hir::{AsAssocItem, AssocItem, Crate, MacroDef, Module, ModuleDef, PrefixKind, Semantics}; 3 AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, MacroDef, ModPath, Module,
4 ModuleDef, PathResolution, PrefixKind, ScopeDef, Semantics, Type,
5};
6use itertools::Itertools;
4use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
5use syntax::{ast, AstNode}; 8use syntax::{ast, utils::path_to_string_stripping_turbo_fish, AstNode, SyntaxNode};
6 9
7use crate::{ 10use crate::{
8 imports_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT}, 11 items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
9 RootDatabase, 12 RootDatabase,
10}; 13};
11 14
15use super::item_name;
16
17/// A candidate for import, derived during various IDE activities:
18/// * completion with imports on the fly proposals
19/// * completion edit resolve requests
20/// * assists
21/// * etc.
12#[derive(Debug)] 22#[derive(Debug)]
13pub enum ImportCandidate { 23pub enum ImportCandidate {
14 // A path, qualified (`std::collections::HashMap`) or not (`HashMap`). 24 /// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
15 Path(PathImportCandidate), 25 Path(PathImportCandidate),
16 /// A trait associated function (with no self parameter) or associated constant. 26 /// A trait associated function (with no self parameter) or an associated constant.
17 /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type 27 /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
18 /// and `name` is the `test_function` 28 /// and `name` is the `test_function`
19 TraitAssocItem(TraitImportCandidate), 29 TraitAssocItem(TraitImportCandidate),
@@ -23,21 +33,40 @@ pub enum ImportCandidate {
23 TraitMethod(TraitImportCandidate), 33 TraitMethod(TraitImportCandidate),
24} 34}
25 35
36/// A trait import needed for a given associated item access.
37/// For `some::path::SomeStruct::ASSOC_`, contains the
38/// type of `some::path::SomeStruct` and `ASSOC_` as the item name.
26#[derive(Debug)] 39#[derive(Debug)]
27pub struct TraitImportCandidate { 40pub struct TraitImportCandidate {
28 pub receiver_ty: hir::Type, 41 /// A type of the item that has the associated item accessed at.
29 pub name: NameToImport, 42 pub receiver_ty: Type,
43 /// The associated item name that the trait to import should contain.
44 pub assoc_item_name: NameToImport,
30} 45}
31 46
47/// Path import for a given name, qualified or not.
32#[derive(Debug)] 48#[derive(Debug)]
33pub struct PathImportCandidate { 49pub struct PathImportCandidate {
34 pub qualifier: Option<ast::Path>, 50 /// Optional qualifier before name.
51 pub qualifier: Option<FirstSegmentUnresolved>,
52 /// The name the item (struct, trait, enum, etc.) should have.
35 pub name: NameToImport, 53 pub name: NameToImport,
36} 54}
37 55
56/// A qualifier that has a first segment and it's unresolved.
57#[derive(Debug)]
58pub struct FirstSegmentUnresolved {
59 fist_segment: ast::NameRef,
60 full_qualifier: ast::Path,
61}
62
63/// A name that will be used during item lookups.
38#[derive(Debug)] 64#[derive(Debug)]
39pub enum NameToImport { 65pub enum NameToImport {
66 /// Requires items with names that exactly match the given string, case-sensitive.
40 Exact(String), 67 Exact(String),
68 /// Requires items with names that case-insensitively contain all letters from the string,
69 /// in the same order, but not necessary adjacent.
41 Fuzzy(String), 70 Fuzzy(String),
42} 71}
43 72
@@ -50,10 +79,12 @@ impl NameToImport {
50 } 79 }
51} 80}
52 81
82/// A struct to find imports in the project, given a certain name (or its part) and the context.
53#[derive(Debug)] 83#[derive(Debug)]
54pub struct ImportAssets { 84pub struct ImportAssets {
55 import_candidate: ImportCandidate, 85 import_candidate: ImportCandidate,
56 module_with_candidate: hir::Module, 86 candidate_node: SyntaxNode,
87 module_with_candidate: Module,
57} 88}
58 89
59impl ImportAssets { 90impl ImportAssets {
@@ -61,9 +92,11 @@ impl ImportAssets {
61 method_call: &ast::MethodCallExpr, 92 method_call: &ast::MethodCallExpr,
62 sema: &Semantics<RootDatabase>, 93 sema: &Semantics<RootDatabase>,
63 ) -> Option<Self> { 94 ) -> Option<Self> {
95 let candidate_node = method_call.syntax().clone();
64 Some(Self { 96 Some(Self {
65 import_candidate: ImportCandidate::for_method_call(sema, method_call)?, 97 import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
66 module_with_candidate: sema.scope(method_call.syntax()).module()?, 98 module_with_candidate: sema.scope(&candidate_node).module()?,
99 candidate_node,
67 }) 100 })
68 } 101 }
69 102
@@ -71,94 +104,94 @@ impl ImportAssets {
71 fully_qualified_path: &ast::Path, 104 fully_qualified_path: &ast::Path,
72 sema: &Semantics<RootDatabase>, 105 sema: &Semantics<RootDatabase>,
73 ) -> Option<Self> { 106 ) -> Option<Self> {
74 let syntax_under_caret = fully_qualified_path.syntax(); 107 let candidate_node = fully_qualified_path.syntax().clone();
75 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { 108 if candidate_node.ancestors().find_map(ast::Use::cast).is_some() {
76 return None; 109 return None;
77 } 110 }
78 Some(Self { 111 Some(Self {
79 import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?, 112 import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
80 module_with_candidate: sema.scope(syntax_under_caret).module()?, 113 module_with_candidate: sema.scope(&candidate_node).module()?,
114 candidate_node,
81 }) 115 })
82 } 116 }
83 117
84 pub fn for_fuzzy_path( 118 pub fn for_fuzzy_path(
85 module_with_path: Module, 119 module_with_candidate: Module,
86 qualifier: Option<ast::Path>, 120 qualifier: Option<ast::Path>,
87 fuzzy_name: String, 121 fuzzy_name: String,
88 sema: &Semantics<RootDatabase>, 122 sema: &Semantics<RootDatabase>,
123 candidate_node: SyntaxNode,
89 ) -> Option<Self> { 124 ) -> Option<Self> {
90 Some(match qualifier { 125 Some(Self {
91 Some(qualifier) => { 126 import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?,
92 let qualifier_resolution = sema.resolve_path(&qualifier)?; 127 module_with_candidate,
93 match qualifier_resolution { 128 candidate_node,
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 },
117 }) 129 })
118 } 130 }
119 131
120 pub fn for_fuzzy_method_call( 132 pub fn for_fuzzy_method_call(
121 module_with_method_call: Module, 133 module_with_method_call: Module,
122 receiver_ty: hir::Type, 134 receiver_ty: Type,
123 fuzzy_method_name: String, 135 fuzzy_method_name: String,
136 candidate_node: SyntaxNode,
124 ) -> Option<Self> { 137 ) -> Option<Self> {
125 Some(Self { 138 Some(Self {
126 import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate { 139 import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
127 receiver_ty, 140 receiver_ty,
128 name: NameToImport::Fuzzy(fuzzy_method_name), 141 assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
129 }), 142 }),
130 module_with_candidate: module_with_method_call, 143 module_with_candidate: module_with_method_call,
144 candidate_node,
131 }) 145 })
132 } 146 }
133} 147}
134 148
149/// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
150/// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
151#[derive(Debug, Clone, PartialEq, Eq, Hash)]
152pub struct LocatedImport {
153 /// The path to use in the `use` statement for a given candidate to be imported.
154 pub import_path: ModPath,
155 /// An item that will be imported with the import path given.
156 pub item_to_import: ItemInNs,
157 /// The path import candidate, resolved.
158 ///
159 /// Not necessary matches the import:
160 /// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_`
161 /// the original item is the associated constant, but the import has to be a trait that
162 /// defines this constant.
163 pub original_item: ItemInNs,
164 /// A path of the original item.
165 pub original_path: Option<ModPath>,
166}
167
168impl LocatedImport {
169 pub fn new(
170 import_path: ModPath,
171 item_to_import: ItemInNs,
172 original_item: ItemInNs,
173 original_path: Option<ModPath>,
174 ) -> Self {
175 Self { import_path, item_to_import, original_item, original_path }
176 }
177}
178
135impl ImportAssets { 179impl ImportAssets {
136 pub fn import_candidate(&self) -> &ImportCandidate { 180 pub fn import_candidate(&self) -> &ImportCandidate {
137 &self.import_candidate 181 &self.import_candidate
138 } 182 }
139 183
140 fn name_to_import(&self) -> &NameToImport {
141 match &self.import_candidate {
142 ImportCandidate::Path(candidate) => &candidate.name,
143 ImportCandidate::TraitAssocItem(candidate)
144 | ImportCandidate::TraitMethod(candidate) => &candidate.name,
145 }
146 }
147
148 pub fn search_for_imports( 184 pub fn search_for_imports(
149 &self, 185 &self,
150 sema: &Semantics<RootDatabase>, 186 sema: &Semantics<RootDatabase>,
151 prefix_kind: PrefixKind, 187 prefix_kind: PrefixKind,
152 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 188 ) -> Vec<LocatedImport> {
153 let _p = profile::span("import_assets::search_for_imports"); 189 let _p = profile::span("import_assets::search_for_imports");
154 self.search_for(sema, Some(prefix_kind)) 190 self.search_for(sema, Some(prefix_kind))
155 } 191 }
156 192
157 /// This may return non-absolute paths if a part of the returned path is already imported into scope. 193 /// This may return non-absolute paths if a part of the returned path is already imported into scope.
158 pub fn search_for_relative_paths( 194 pub fn search_for_relative_paths(&self, sema: &Semantics<RootDatabase>) -> Vec<LocatedImport> {
159 &self,
160 sema: &Semantics<RootDatabase>,
161 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
162 let _p = profile::span("import_assets::search_for_relative_paths"); 195 let _p = profile::span("import_assets::search_for_relative_paths");
163 self.search_for(sema, None) 196 self.search_for(sema, None)
164 } 197 }
@@ -166,29 +199,29 @@ impl ImportAssets {
166 fn search_for( 199 fn search_for(
167 &self, 200 &self,
168 sema: &Semantics<RootDatabase>, 201 sema: &Semantics<RootDatabase>,
169 prefixed: Option<hir::PrefixKind>, 202 prefixed: Option<PrefixKind>,
170 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 203 ) -> Vec<LocatedImport> {
171 let current_crate = self.module_with_candidate.krate(); 204 let items_with_candidate_name = match self.name_to_import() {
172 205 NameToImport::Exact(exact_name) => items_locator::with_exact_name(
173 let unfiltered_imports = match self.name_to_import() { 206 sema,
174 NameToImport::Exact(exact_name) => { 207 self.module_with_candidate.krate(),
175 imports_locator::find_exact_imports(sema, current_crate, exact_name.clone()) 208 exact_name.clone(),
176 } 209 ),
177 // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items: 210 // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
178 // instead, we need to look up all trait impls for a certain struct and search through them only 211 // instead, we need to look up all trait impls for a certain struct and search through them only
179 // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032 212 // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032
180 // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup 213 // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup
181 // for the details 214 // for the details
182 NameToImport::Fuzzy(fuzzy_name) => { 215 NameToImport::Fuzzy(fuzzy_name) => {
183 let (assoc_item_search, limit) = match self.import_candidate { 216 let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() {
184 ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => { 217 (AssocItemSearch::AssocItemsOnly, None)
185 (AssocItemSearch::AssocItemsOnly, None) 218 } else {
186 } 219 (AssocItemSearch::Include, Some(DEFAULT_QUERY_SEARCH_LIMIT))
187 _ => (AssocItemSearch::Exclude, Some(DEFAULT_QUERY_SEARCH_LIMIT)),
188 }; 220 };
189 imports_locator::find_similar_imports( 221
222 items_locator::with_similar_name(
190 sema, 223 sema,
191 current_crate, 224 self.module_with_candidate.krate(),
192 fuzzy_name.clone(), 225 fuzzy_name.clone(),
193 assoc_item_search, 226 assoc_item_search,
194 limit, 227 limit,
@@ -196,63 +229,224 @@ impl ImportAssets {
196 } 229 }
197 }; 230 };
198 231
199 let db = sema.db; 232 let scope_definitions = self.scope_definitions(sema);
200 let mut res = 233 self.applicable_defs(sema.db, prefixed, items_with_candidate_name)
201 applicable_defs(self.import_candidate(), current_crate, db, unfiltered_imports) 234 .into_iter()
202 .filter_map(|candidate| { 235 .filter(|import| import.import_path.len() > 1)
203 let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into); 236 .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import)))
204 237 .sorted_by_key(|import| import.import_path.clone())
205 let item_to_search = match self.import_candidate { 238 .collect()
206 ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => { 239 }
207 let canidate_trait = match candidate { 240
208 Either::Left(module_def) => { 241 fn scope_definitions(&self, sema: &Semantics<RootDatabase>) -> FxHashSet<ScopeDef> {
209 module_def.as_assoc_item(db)?.containing_trait(db) 242 let mut scope_definitions = FxHashSet::default();
210 } 243 sema.scope(&self.candidate_node).process_all_names(&mut |_, scope_def| {
211 _ => None, 244 scope_definitions.insert(scope_def);
212 }?; 245 });
213 ModuleDef::from(canidate_trait).into() 246 scope_definitions
214 } 247 }
215 _ => item, 248
216 }; 249 fn name_to_import(&self) -> &NameToImport {
217 250 match &self.import_candidate {
218 if let Some(prefix_kind) = prefixed { 251 ImportCandidate::Path(candidate) => &candidate.name,
219 self.module_with_candidate.find_use_path_prefixed( 252 ImportCandidate::TraitAssocItem(candidate)
220 db, 253 | ImportCandidate::TraitMethod(candidate) => &candidate.assoc_item_name,
221 item_to_search, 254 }
222 prefix_kind, 255 }
223 ) 256
224 } else { 257 fn applicable_defs(
225 self.module_with_candidate.find_use_path(db, item_to_search) 258 &self,
226 } 259 db: &RootDatabase,
227 .map(|path| (path, item)) 260 prefixed: Option<PrefixKind>,
228 }) 261 items_with_candidate_name: FxHashSet<ItemInNs>,
229 .filter(|(use_path, _)| use_path.len() > 1) 262 ) -> FxHashSet<LocatedImport> {
230 .collect::<Vec<_>>(); 263 let _p = profile::span("import_assets::applicable_defs");
231 res.sort_by_cached_key(|(path, _)| path.clone()); 264 let current_crate = self.module_with_candidate.krate();
232 res 265
266 let mod_path = |item| {
267 get_mod_path(db, item_for_path_search(db, item)?, &self.module_with_candidate, prefixed)
268 };
269
270 match &self.import_candidate {
271 ImportCandidate::Path(path_candidate) => {
272 path_applicable_imports(db, path_candidate, mod_path, items_with_candidate_name)
273 }
274 ImportCandidate::TraitAssocItem(trait_candidate) => trait_applicable_items(
275 db,
276 current_crate,
277 trait_candidate,
278 true,
279 mod_path,
280 items_with_candidate_name,
281 ),
282 ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items(
283 db,
284 current_crate,
285 trait_candidate,
286 false,
287 mod_path,
288 items_with_candidate_name,
289 ),
290 }
233 } 291 }
234} 292}
235 293
236fn applicable_defs<'a>( 294fn path_applicable_imports(
237 import_candidate: &ImportCandidate,
238 current_crate: Crate,
239 db: &RootDatabase, 295 db: &RootDatabase,
240 unfiltered_imports: Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a>, 296 path_candidate: &PathImportCandidate,
241) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> { 297 mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
242 let receiver_ty = match import_candidate { 298 items_with_candidate_name: FxHashSet<ItemInNs>,
243 ImportCandidate::Path(_) => return unfiltered_imports, 299) -> FxHashSet<LocatedImport> {
244 ImportCandidate::TraitAssocItem(candidate) | ImportCandidate::TraitMethod(candidate) => { 300 let _p = profile::span("import_assets::path_applicable_imports");
245 &candidate.receiver_ty 301
302 let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier {
303 None => {
304 return items_with_candidate_name
305 .into_iter()
306 .filter_map(|item| {
307 Some(LocatedImport::new(mod_path(item)?, item, item, mod_path(item)))
308 })
309 .collect();
246 } 310 }
311 Some(first_segment_unresolved) => (
312 first_segment_unresolved.fist_segment.to_string(),
313 path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier),
314 ),
247 }; 315 };
248 316
317 items_with_candidate_name
318 .into_iter()
319 .filter_map(|item| {
320 import_for_item(db, mod_path, &unresolved_first_segment, &unresolved_qualifier, item)
321 })
322 .collect()
323}
324
325fn import_for_item(
326 db: &RootDatabase,
327 mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
328 unresolved_first_segment: &str,
329 unresolved_qualifier: &str,
330 original_item: ItemInNs,
331) -> Option<LocatedImport> {
332 let _p = profile::span("import_assets::import_for_item");
333
334 let original_item_candidate = item_for_path_search(db, original_item)?;
335 let import_path_candidate = mod_path(original_item_candidate)?;
336 let import_path_string = import_path_candidate.to_string();
337
338 let expected_import_end = if item_as_assoc(db, original_item).is_some() {
339 unresolved_qualifier.to_string()
340 } else {
341 format!("{}::{}", unresolved_qualifier, item_name(db, original_item)?)
342 };
343 if !import_path_string.contains(unresolved_first_segment)
344 || !import_path_string.ends_with(&expected_import_end)
345 {
346 return None;
347 }
348
349 let segment_import =
350 find_import_for_segment(db, original_item_candidate, &unresolved_first_segment)?;
351 let trait_item_to_import = item_as_assoc(db, original_item)
352 .and_then(|assoc| assoc.containing_trait(db))
353 .map(|trait_| ItemInNs::from(ModuleDef::from(trait_)));
354 Some(match (segment_import == original_item_candidate, trait_item_to_import) {
355 (true, Some(_)) => {
356 // FIXME we should be able to import both the trait and the segment,
357 // but it's unclear what to do with overlapping edits (merge imports?)
358 // especially in case of lazy completion edit resolutions.
359 return None;
360 }
361 (false, Some(trait_to_import)) => LocatedImport::new(
362 mod_path(trait_to_import)?,
363 trait_to_import,
364 original_item,
365 mod_path(original_item),
366 ),
367 (true, None) => LocatedImport::new(
368 import_path_candidate,
369 original_item_candidate,
370 original_item,
371 mod_path(original_item),
372 ),
373 (false, None) => LocatedImport::new(
374 mod_path(segment_import)?,
375 segment_import,
376 original_item,
377 mod_path(original_item),
378 ),
379 })
380}
381
382fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
383 Some(match item {
384 ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
385 Some(assoc_item) => match assoc_item.container(db) {
386 AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
387 AssocItemContainer::Impl(impl_) => {
388 ItemInNs::from(ModuleDef::from(impl_.target_ty(db).as_adt()?))
389 }
390 },
391 None => item,
392 },
393 ItemInNs::Macros(_) => item,
394 })
395}
396
397fn find_import_for_segment(
398 db: &RootDatabase,
399 original_item: ItemInNs,
400 unresolved_first_segment: &str,
401) -> Option<ItemInNs> {
402 let segment_is_name = item_name(db, original_item)
403 .map(|name| name.to_string() == unresolved_first_segment)
404 .unwrap_or(false);
405
406 Some(if segment_is_name {
407 original_item
408 } else {
409 let matching_module =
410 module_with_segment_name(db, &unresolved_first_segment, original_item)?;
411 ItemInNs::from(ModuleDef::from(matching_module))
412 })
413}
414
415fn module_with_segment_name(
416 db: &RootDatabase,
417 segment_name: &str,
418 candidate: ItemInNs,
419) -> Option<Module> {
420 let mut current_module = match candidate {
421 ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).module(db),
422 ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).module(db),
423 ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).module(db),
424 };
425 while let Some(module) = current_module {
426 if let Some(module_name) = module.name(db) {
427 if module_name.to_string() == segment_name {
428 return Some(module);
429 }
430 }
431 current_module = module.parent(db);
432 }
433 None
434}
435
436fn trait_applicable_items(
437 db: &RootDatabase,
438 current_crate: Crate,
439 trait_candidate: &TraitImportCandidate,
440 trait_assoc_item: bool,
441 mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
442 items_with_candidate_name: FxHashSet<ItemInNs>,
443) -> FxHashSet<LocatedImport> {
444 let _p = profile::span("import_assets::trait_applicable_items");
249 let mut required_assoc_items = FxHashSet::default(); 445 let mut required_assoc_items = FxHashSet::default();
250 446
251 let trait_candidates = unfiltered_imports 447 let trait_candidates = items_with_candidate_name
252 .filter_map(|input| match input { 448 .into_iter()
253 Either::Left(module_def) => module_def.as_assoc_item(db), 449 .filter_map(|input| item_as_assoc(db, input))
254 _ => None,
255 })
256 .filter_map(|assoc| { 450 .filter_map(|assoc| {
257 let assoc_item_trait = assoc.containing_trait(db)?; 451 let assoc_item_trait = assoc.containing_trait(db)?;
258 required_assoc_items.insert(assoc); 452 required_assoc_items.insert(assoc);
@@ -260,11 +454,10 @@ fn applicable_defs<'a>(
260 }) 454 })
261 .collect(); 455 .collect();
262 456
263 let mut applicable_defs = FxHashSet::default(); 457 let mut located_imports = FxHashSet::default();
264 458
265 match import_candidate { 459 if trait_assoc_item {
266 ImportCandidate::Path(_) => unreachable!(), 460 trait_candidate.receiver_ty.iterate_path_candidates(
267 ImportCandidate::TraitAssocItem(_) => receiver_ty.iterate_path_candidates(
268 db, 461 db,
269 current_crate, 462 current_crate,
270 &trait_candidates, 463 &trait_candidates,
@@ -276,12 +469,21 @@ fn applicable_defs<'a>(
276 return None; 469 return None;
277 } 470 }
278 } 471 }
279 applicable_defs.insert(Either::Left(assoc_to_module_def(assoc))); 472
473 let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?));
474 let original_item = assoc_to_item(assoc);
475 located_imports.insert(LocatedImport::new(
476 mod_path(item)?,
477 item,
478 original_item,
479 mod_path(original_item),
480 ));
280 } 481 }
281 None::<()> 482 None::<()>
282 }, 483 },
283 ), 484 )
284 ImportCandidate::TraitMethod(_) => receiver_ty.iterate_method_candidates( 485 } else {
486 trait_candidate.receiver_ty.iterate_method_candidates(
285 db, 487 db,
286 current_crate, 488 current_crate,
287 &trait_candidates, 489 &trait_candidates,
@@ -289,21 +491,41 @@ fn applicable_defs<'a>(
289 |_, function| { 491 |_, function| {
290 let assoc = function.as_assoc_item(db)?; 492 let assoc = function.as_assoc_item(db)?;
291 if required_assoc_items.contains(&assoc) { 493 if required_assoc_items.contains(&assoc) {
292 applicable_defs.insert(Either::Left(assoc_to_module_def(assoc))); 494 let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?));
495 let original_item = assoc_to_item(assoc);
496 located_imports.insert(LocatedImport::new(
497 mod_path(item)?,
498 item,
499 original_item,
500 mod_path(original_item),
501 ));
293 } 502 }
294 None::<()> 503 None::<()>
295 }, 504 },
296 ), 505 )
297 }; 506 };
298 507
299 Box::new(applicable_defs.into_iter()) 508 located_imports
300} 509}
301 510
302fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef { 511fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
303 match assoc { 512 match assoc {
304 AssocItem::Function(f) => f.into(), 513 AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
305 AssocItem::Const(c) => c.into(), 514 AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
306 AssocItem::TypeAlias(t) => t.into(), 515 AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
516 }
517}
518
519fn get_mod_path(
520 db: &RootDatabase,
521 item_to_search: ItemInNs,
522 module_with_candidate: &Module,
523 prefixed: Option<PrefixKind>,
524) -> Option<ModPath> {
525 if let Some(prefix_kind) = prefixed {
526 module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind)
527 } else {
528 module_with_candidate.find_use_path(db, item_to_search)
307 } 529 }
308} 530}
309 531
@@ -316,7 +538,7 @@ impl ImportCandidate {
316 Some(_) => None, 538 Some(_) => None,
317 None => Some(Self::TraitMethod(TraitImportCandidate { 539 None => Some(Self::TraitMethod(TraitImportCandidate {
318 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?, 540 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
319 name: NameToImport::Exact(method_call.name_ref()?.to_string()), 541 assoc_item_name: NameToImport::Exact(method_call.name_ref()?.to_string()),
320 })), 542 })),
321 } 543 }
322 } 544 }
@@ -325,41 +547,63 @@ impl ImportCandidate {
325 if sema.resolve_path(path).is_some() { 547 if sema.resolve_path(path).is_some() {
326 return None; 548 return None;
327 } 549 }
550 path_import_candidate(
551 sema,
552 path.qualifier(),
553 NameToImport::Exact(path.segment()?.name_ref()?.to_string()),
554 )
555 }
328 556
329 let segment = path.segment()?; 557 fn for_fuzzy_path(
330 let candidate = if let Some(qualifier) = path.qualifier() { 558 qualifier: Option<ast::Path>,
331 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; 559 fuzzy_name: String,
332 let qualifier_start_path = 560 sema: &Semantics<RootDatabase>,
333 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; 561 ) -> Option<Self> {
334 if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) { 562 path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
335 let qualifier_resolution = if qualifier_start_path == qualifier { 563 }
336 qualifier_start_resolution 564
565 fn is_trait_candidate(&self) -> bool {
566 matches!(self, ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_))
567 }
568}
569
570fn path_import_candidate(
571 sema: &Semantics<RootDatabase>,
572 qualifier: Option<ast::Path>,
573 name: NameToImport,
574) -> Option<ImportCandidate> {
575 Some(match qualifier {
576 Some(qualifier) => match sema.resolve_path(&qualifier) {
577 None => {
578 let qualifier_start =
579 qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
580 let qualifier_start_path =
581 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
582 if sema.resolve_path(&qualifier_start_path).is_none() {
583 ImportCandidate::Path(PathImportCandidate {
584 qualifier: Some(FirstSegmentUnresolved {
585 fist_segment: qualifier_start,
586 full_qualifier: qualifier,
587 }),
588 name,
589 })
337 } else { 590 } else {
338 sema.resolve_path(&qualifier)? 591 return None;
339 };
340 match qualifier_resolution {
341 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
342 ImportCandidate::TraitAssocItem(TraitImportCandidate {
343 receiver_ty: assoc_item_path.ty(sema.db),
344 name: NameToImport::Exact(segment.name_ref()?.to_string()),
345 })
346 }
347 _ => return None,
348 } 592 }
349 } else { 593 }
350 ImportCandidate::Path(PathImportCandidate { 594 Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
351 qualifier: Some(qualifier), 595 ImportCandidate::TraitAssocItem(TraitImportCandidate {
352 name: NameToImport::Exact(qualifier_start.to_string()), 596 receiver_ty: assoc_item_path.ty(sema.db),
597 assoc_item_name: name,
353 }) 598 })
354 } 599 }
355 } else { 600 Some(_) => return None,
356 ImportCandidate::Path(PathImportCandidate { 601 },
357 qualifier: None, 602 None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
358 name: NameToImport::Exact( 603 })
359 segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(), 604}
360 ), 605
361 }) 606fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
362 }; 607 item.as_module_def_id()
363 Some(candidate) 608 .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db))
364 }
365} 609}
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/items_locator.rs
index 502e8281a..8a7f02935 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/items_locator.rs
@@ -1,9 +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 either::Either;
4use hir::{ 5use hir::{
5 import_map::{self, ImportKind}, 6 import_map::{self, ImportKind},
6 AsAssocItem, Crate, MacroDef, ModuleDef, Semantics, 7 AsAssocItem, Crate, ItemInNs, ModuleDef, Semantics,
7}; 8};
8use syntax::{ast, AstNode, SyntaxKind::NAME}; 9use syntax::{ast, AstNode, SyntaxKind::NAME};
9 10
@@ -12,47 +13,47 @@ use crate::{
12 symbol_index::{self, FileSymbol}, 13 symbol_index::{self, FileSymbol},
13 RootDatabase, 14 RootDatabase,
14}; 15};
15use either::Either;
16use rustc_hash::FxHashSet; 16use rustc_hash::FxHashSet;
17 17
18pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40; 18pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40;
19 19
20pub fn find_exact_imports<'a>( 20pub fn with_exact_name(
21 sema: &Semantics<'a, RootDatabase>, 21 sema: &Semantics<'_, RootDatabase>,
22 krate: Crate, 22 krate: Crate,
23 name_to_import: String, 23 exact_name: String,
24) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>>> { 24) -> FxHashSet<ItemInNs> {
25 let _p = profile::span("find_exact_imports"); 25 let _p = profile::span("find_exact_imports");
26 Box::new(find_imports( 26 find_items(
27 sema, 27 sema,
28 krate, 28 krate,
29 { 29 {
30 let mut local_query = symbol_index::Query::new(name_to_import.clone()); 30 let mut local_query = symbol_index::Query::new(exact_name.clone());
31 local_query.exact(); 31 local_query.exact();
32 local_query.limit(DEFAULT_QUERY_SEARCH_LIMIT); 32 local_query.limit(DEFAULT_QUERY_SEARCH_LIMIT);
33 local_query 33 local_query
34 }, 34 },
35 import_map::Query::new(name_to_import) 35 import_map::Query::new(exact_name)
36 .limit(DEFAULT_QUERY_SEARCH_LIMIT) 36 .limit(DEFAULT_QUERY_SEARCH_LIMIT)
37 .name_only() 37 .name_only()
38 .search_mode(import_map::SearchMode::Equals) 38 .search_mode(import_map::SearchMode::Equals)
39 .case_sensitive(), 39 .case_sensitive(),
40 )) 40 )
41} 41}
42 42
43#[derive(Debug)]
43pub enum AssocItemSearch { 44pub enum AssocItemSearch {
44 Include, 45 Include,
45 Exclude, 46 Exclude,
46 AssocItemsOnly, 47 AssocItemsOnly,
47} 48}
48 49
49pub fn find_similar_imports<'a>( 50pub fn with_similar_name(
50 sema: &Semantics<'a, RootDatabase>, 51 sema: &Semantics<'_, RootDatabase>,
51 krate: Crate, 52 krate: Crate,
52 fuzzy_search_string: String, 53 fuzzy_search_string: String,
53 assoc_item_search: AssocItemSearch, 54 assoc_item_search: AssocItemSearch,
54 limit: Option<usize>, 55 limit: Option<usize>,
55) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> { 56) -> FxHashSet<ItemInNs> {
56 let _p = profile::span("find_similar_imports"); 57 let _p = profile::span("find_similar_imports");
57 58
58 let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) 59 let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
@@ -76,37 +77,39 @@ pub fn find_similar_imports<'a>(
76 local_query.limit(limit); 77 local_query.limit(limit);
77 } 78 }
78 79
79 let db = sema.db; 80 find_items(sema, krate, local_query, external_query)
80 Box::new(find_imports(sema, krate, local_query, external_query).filter( 81 .into_iter()
81 move |import_candidate| match assoc_item_search { 82 .filter(move |&item| match assoc_item_search {
82 AssocItemSearch::Include => true, 83 AssocItemSearch::Include => true,
83 AssocItemSearch::Exclude => !is_assoc_item(import_candidate, db), 84 AssocItemSearch::Exclude => !is_assoc_item(item, sema.db),
84 AssocItemSearch::AssocItemsOnly => is_assoc_item(import_candidate, db), 85 AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db),
85 }, 86 })
86 )) 87 .collect()
87} 88}
88 89
89fn is_assoc_item(import_candidate: &Either<ModuleDef, MacroDef>, db: &RootDatabase) -> bool { 90fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool {
90 match import_candidate { 91 item.as_module_def_id()
91 Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_some(), 92 .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db))
92 Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_some(), 93 .is_some()
93 Either::Left(ModuleDef::TypeAlias(type_alias)) => type_alias.as_assoc_item(db).is_some(),
94 _ => false,
95 }
96} 94}
97 95
98fn find_imports<'a>( 96fn find_items(
99 sema: &Semantics<'a, RootDatabase>, 97 sema: &Semantics<'_, RootDatabase>,
100 krate: Crate, 98 krate: Crate,
101 local_query: symbol_index::Query, 99 local_query: symbol_index::Query,
102 external_query: import_map::Query, 100 external_query: import_map::Query,
103) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 101) -> FxHashSet<ItemInNs> {
104 let _p = profile::span("find_similar_imports"); 102 let _p = profile::span("find_similar_imports");
105 let db = sema.db; 103 let db = sema.db;
106 104
107 // Query dependencies first. 105 // Query dependencies first.
108 let mut candidates: FxHashSet<_> = 106 let mut candidates = krate
109 krate.query_external_importables(db, external_query).collect(); 107 .query_external_importables(db, external_query)
108 .map(|external_importable| match external_importable {
109 Either::Left(module_def) => ItemInNs::from(module_def),
110 Either::Right(macro_def) => ItemInNs::from(macro_def),
111 })
112 .collect::<FxHashSet<_>>();
110 113
111 // Query the local crate using the symbol index. 114 // Query the local crate using the symbol index.
112 let local_results = symbol_index::crate_symbols(db, krate.into(), local_query); 115 let local_results = symbol_index::crate_symbols(db, krate.into(), local_query);
@@ -114,19 +117,19 @@ fn find_imports<'a>(
114 candidates.extend( 117 candidates.extend(
115 local_results 118 local_results
116 .into_iter() 119 .into_iter()
117 .filter_map(|import_candidate| get_name_definition(sema, &import_candidate)) 120 .filter_map(|local_candidate| get_name_definition(sema, &local_candidate))
118 .filter_map(|name_definition_to_import| match name_definition_to_import { 121 .filter_map(|name_definition_to_import| match name_definition_to_import {
119 Definition::ModuleDef(module_def) => Some(Either::Left(module_def)), 122 Definition::ModuleDef(module_def) => Some(ItemInNs::from(module_def)),
120 Definition::Macro(macro_def) => Some(Either::Right(macro_def)), 123 Definition::Macro(macro_def) => Some(ItemInNs::from(macro_def)),
121 _ => None, 124 _ => None,
122 }), 125 }),
123 ); 126 );
124 127
125 candidates.into_iter() 128 candidates
126} 129}
127 130
128fn get_name_definition<'a>( 131fn get_name_definition(
129 sema: &Semantics<'a, RootDatabase>, 132 sema: &Semantics<'_, RootDatabase>,
130 import_candidate: &FileSymbol, 133 import_candidate: &FileSymbol,
131) -> Option<Definition> { 134) -> Option<Definition> {
132 let _p = profile::span("get_name_definition"); 135 let _p = profile::span("get_name_definition");
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index 6eb34b06b..88ee4a87d 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -8,7 +8,7 @@ pub mod line_index;
8pub mod symbol_index; 8pub mod symbol_index;
9pub mod defs; 9pub mod defs;
10pub mod search; 10pub mod search;
11pub mod imports_locator; 11pub mod items_locator;
12pub mod source_change; 12pub mod source_change;
13pub mod ty_filter; 13pub mod ty_filter;
14pub mod traits; 14pub mod traits;
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 28e221271..25df13554 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -16,7 +16,6 @@ use ide_db::helpers::{
16 insert_use::{InsertUseConfig, MergeBehavior}, 16 insert_use::{InsertUseConfig, MergeBehavior},
17 SnippetCap, 17 SnippetCap,
18}; 18};
19use itertools::Itertools;
20use lsp_types::{ClientCapabilities, MarkupKind}; 19use lsp_types::{ClientCapabilities, MarkupKind};
21use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource}; 20use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource};
22use rustc_hash::FxHashSet; 21use rustc_hash::FxHashSet;
@@ -98,13 +97,15 @@ config_data! {
98 diagnostics_enableExperimental: bool = "true", 97 diagnostics_enableExperimental: bool = "true",
99 /// List of rust-analyzer diagnostics to disable. 98 /// List of rust-analyzer diagnostics to disable.
100 diagnostics_disabled: FxHashSet<String> = "[]", 99 diagnostics_disabled: FxHashSet<String> = "[]",
101 /// List of warnings that should be displayed with info severity.\n\nThe 100 /// List of warnings that should be displayed with info severity.
102 /// warnings will be indicated by a blue squiggly underline in code and 101 ///
103 /// a blue icon in the `Problems Panel`. 102 /// The warnings will be indicated by a blue squiggly underline in code
103 /// and a blue icon in the `Problems Panel`.
104 diagnostics_warningsAsHint: Vec<String> = "[]", 104 diagnostics_warningsAsHint: Vec<String> = "[]",
105 /// List of warnings that should be displayed with hint severity.\n\nThe 105 /// List of warnings that should be displayed with hint severity.
106 /// warnings will be indicated by faded text or three dots in code and 106 ///
107 /// will not show up in the `Problems Panel`. 107 /// The warnings will be indicated by faded text or three dots in code
108 /// and will not show up in the `Problems Panel`.
108 diagnostics_warningsAsInfo: Vec<String> = "[]", 109 diagnostics_warningsAsInfo: Vec<String> = "[]",
109 110
110 /// Controls file watching implementation. 111 /// Controls file watching implementation.
@@ -158,7 +159,9 @@ config_data! {
158 lens_references: bool = "false", 159 lens_references: bool = "false",
159 160
160 /// Disable project auto-discovery in favor of explicitly specified set 161 /// Disable project auto-discovery in favor of explicitly specified set
161 /// of projects.\n\nElements must be paths pointing to `Cargo.toml`, 162 /// of projects.
163 ///
164 /// Elements must be paths pointing to `Cargo.toml`,
162 /// `rust-project.json`, or JSON objects in `rust-project.json` format. 165 /// `rust-project.json`, or JSON objects in `rust-project.json` format.
163 linkedProjects: Vec<ManifestOrProjectJson> = "[]", 166 linkedProjects: Vec<ManifestOrProjectJson> = "[]",
164 167
@@ -177,7 +180,7 @@ config_data! {
177 /// Command to be executed instead of 'cargo' for runnables. 180 /// Command to be executed instead of 'cargo' for runnables.
178 runnables_overrideCargo: Option<String> = "null", 181 runnables_overrideCargo: Option<String> = "null",
179 /// Additional arguments to be passed to cargo for runnables such as 182 /// Additional arguments to be passed to cargo for runnables such as
180 /// tests or binaries.\nFor example, it may be `--release`. 183 /// tests or binaries. For example, it may be `--release`.
181 runnables_cargoExtraArgs: Vec<String> = "[]", 184 runnables_cargoExtraArgs: Vec<String> = "[]",
182 185
183 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private 186 /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
@@ -765,7 +768,8 @@ fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json:
765} 768}
766 769
767fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value { 770fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
768 let doc = doc.iter().map(|it| it.trim()).join(" "); 771 let doc = doc_comment_to_string(doc);
772 let doc = doc.trim_end_matches('\n');
769 assert!( 773 assert!(
770 doc.ends_with('.') && doc.starts_with(char::is_uppercase), 774 doc.ends_with('.') && doc.starts_with(char::is_uppercase),
771 "bad docs for {}: {:?}", 775 "bad docs for {}: {:?}",
@@ -854,11 +858,16 @@ fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
854 .iter() 858 .iter()
855 .map(|(field, _ty, doc, default)| { 859 .map(|(field, _ty, doc, default)| {
856 let name = format!("rust-analyzer.{}", field.replace("_", ".")); 860 let name = format!("rust-analyzer.{}", field.replace("_", "."));
857 format!("[[{}]]{} (default: `{}`)::\n{}\n", name, name, default, doc.join(" ")) 861 let doc = doc_comment_to_string(*doc);
862 format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
858 }) 863 })
859 .collect::<String>() 864 .collect::<String>()
860} 865}
861 866
867fn doc_comment_to_string(doc: &[&str]) -> String {
868 doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
869}
870
862#[cfg(test)] 871#[cfg(test)]
863mod tests { 872mod tests {
864 use std::fs; 873 use std::fs;
@@ -901,13 +910,8 @@ mod tests {
901 #[test] 910 #[test]
902 fn generate_config_documentation() { 911 fn generate_config_documentation() {
903 let docs_path = project_root().join("docs/user/generated_config.adoc"); 912 let docs_path = project_root().join("docs/user/generated_config.adoc");
904 let current = fs::read_to_string(&docs_path).unwrap();
905 let expected = ConfigData::manual(); 913 let expected = ConfigData::manual();
906 914 ensure_file_contents(&docs_path, &expected);
907 if remove_ws(&current) != remove_ws(&expected) {
908 fs::write(&docs_path, expected).unwrap();
909 panic!("updated config manual");
910 }
911 } 915 }
912 916
913 fn remove_ws(text: &str) -> String { 917 fn remove_ws(text: &str) -> String {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 4f6f250d6..2c4c339cb 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -697,7 +697,6 @@ pub(crate) fn handle_completion_resolve(
697 FilePosition { file_id, offset }, 697 FilePosition { file_id, offset },
698 &resolve_data.full_import_path, 698 &resolve_data.full_import_path,
699 resolve_data.imported_name, 699 resolve_data.imported_name,
700 resolve_data.import_for_trait_assoc_item,
701 )? 700 )?
702 .into_iter() 701 .into_iter()
703 .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel))) 702 .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
@@ -1525,7 +1524,6 @@ struct CompletionResolveData {
1525 position: lsp_types::TextDocumentPositionParams, 1524 position: lsp_types::TextDocumentPositionParams,
1526 full_import_path: String, 1525 full_import_path: String,
1527 imported_name: String, 1526 imported_name: String,
1528 import_for_trait_assoc_item: bool,
1529} 1527}
1530 1528
1531fn fill_resolve_data( 1529fn fill_resolve_data(
@@ -1534,15 +1532,13 @@ fn fill_resolve_data(
1534 position: &TextDocumentPositionParams, 1532 position: &TextDocumentPositionParams,
1535) -> Option<()> { 1533) -> Option<()> {
1536 let import_edit = item.import_to_add()?; 1534 let import_edit = item.import_to_add()?;
1537 let full_import_path = import_edit.import_path.to_string(); 1535 let import_path = &import_edit.import.import_path;
1538 let imported_name = import_edit.import_path.segments().last()?.to_string();
1539 1536
1540 *resolve_data = Some( 1537 *resolve_data = Some(
1541 to_value(CompletionResolveData { 1538 to_value(CompletionResolveData {
1542 position: position.to_owned(), 1539 position: position.to_owned(),
1543 full_import_path, 1540 full_import_path: import_path.to_string(),
1544 imported_name, 1541 imported_name: import_path.segments().last()?.to_string(),
1545 import_for_trait_assoc_item: import_edit.import_for_trait_assoc_item,
1546 }) 1542 })
1547 .unwrap(), 1543 .unwrap(),
1548 ); 1544 );
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index b6c5de658..70ba8adb4 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -91,6 +91,10 @@ pub fn path_from_segments(
91 }) 91 })
92} 92}
93 93
94pub fn path_from_text(text: &str) -> ast::Path {
95 ast_from_text(&format!("fn main() {{ let test = {}; }}", text))
96}
97
94pub fn glob_use_tree() -> ast::UseTree { 98pub fn glob_use_tree() -> ast::UseTree {
95 ast_from_text("use *;") 99 ast_from_text("use *;")
96} 100}
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index 11294c5b2..09e212e8c 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -37,6 +37,7 @@ pub mod algo;
37pub mod ast; 37pub mod ast;
38#[doc(hidden)] 38#[doc(hidden)]
39pub mod fuzz; 39pub mod fuzz;
40pub mod utils;
40 41
41use std::{marker::PhantomData, sync::Arc}; 42use std::{marker::PhantomData, sync::Arc};
42 43
diff --git a/crates/syntax/src/utils.rs b/crates/syntax/src/utils.rs
new file mode 100644
index 000000000..f4c02518b
--- /dev/null
+++ b/crates/syntax/src/utils.rs
@@ -0,0 +1,43 @@
1//! A set of utils methods to reuse on other abstraction levels
2
3use itertools::Itertools;
4
5use crate::{ast, match_ast, AstNode};
6
7pub fn path_to_string_stripping_turbo_fish(path: &ast::Path) -> String {
8 path.syntax()
9 .children()
10 .filter_map(|node| {
11 match_ast! {
12 match node {
13 ast::PathSegment(it) => {
14 Some(it.name_ref()?.to_string())
15 },
16 ast::Path(it) => {
17 Some(path_to_string_stripping_turbo_fish(&it))
18 },
19 _ => None,
20 }
21 }
22 })
23 .join("::")
24}
25
26#[cfg(test)]
27mod tests {
28 use super::path_to_string_stripping_turbo_fish;
29 use crate::ast::make;
30
31 #[test]
32 fn turbofishes_are_stripped() {
33 assert_eq!("Vec", path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>")),);
34 assert_eq!(
35 "Vec::new",
36 path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>::new")),
37 );
38 assert_eq!(
39 "Vec::new",
40 path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::new()")),
41 );
42 }
43}