aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_db/src')
-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
4 files changed, 462 insertions, 207 deletions
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;