diff options
-rw-r--r-- | crates/ide_assists/src/handlers/qualify_path.rs | 1 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | 35 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/flyimport.rs | 32 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 28 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/import_assets.rs | 215 | ||||
-rw-r--r-- | crates/ide_db/src/items_locator.rs | 163 |
6 files changed, 250 insertions, 224 deletions
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs index e7444f7db..f91770a76 100644 --- a/crates/ide_assists/src/handlers/qualify_path.rs +++ b/crates/ide_assists/src/handlers/qualify_path.rs | |||
@@ -536,6 +536,7 @@ fn main() { | |||
536 | } | 536 | } |
537 | 537 | ||
538 | #[test] | 538 | #[test] |
539 | #[ignore = "FIXME: non-trait assoc items completion is unsupported yet, see FIXME in the import_assets.rs for more details"] | ||
539 | fn associated_struct_const_unqualified() { | 540 | fn associated_struct_const_unqualified() { |
540 | check_assist( | 541 | check_assist( |
541 | qualify_path, | 542 | qualify_path, |
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 88fe2fe90..2608b56da 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,5 @@ | |||
1 | use hir::ModuleDef; | 1 | use hir::ModuleDef; |
2 | use ide_db::helpers::mod_path_to_ast; | 2 | use ide_db::helpers::{import_assets::NameToImport, mod_path_to_ast}; |
3 | use ide_db::items_locator; | 3 | use ide_db::items_locator; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use syntax::{ | 5 | use syntax::{ |
@@ -65,20 +65,25 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
65 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; | 65 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; |
66 | let current_crate = current_module.krate(); | 66 | let current_crate = current_module.krate(); |
67 | 67 | ||
68 | let found_traits = | 68 | let found_traits = items_locator::items_with_name( |
69 | items_locator::with_exact_name(&ctx.sema, current_crate, trait_token.text().to_string()) | 69 | &ctx.sema, |
70 | .into_iter() | 70 | current_crate, |
71 | .filter_map(|item| match ModuleDef::from(item.as_module_def_id()?) { | 71 | NameToImport::Exact(trait_token.text().to_string()), |
72 | ModuleDef::Trait(trait_) => Some(trait_), | 72 | items_locator::AssocItemSearch::Exclude, |
73 | _ => None, | 73 | Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT), |
74 | }) | 74 | ) |
75 | .flat_map(|trait_| { | 75 | .into_iter() |
76 | current_module | 76 | .filter_map(|item| match ModuleDef::from(item.as_module_def_id()?) { |
77 | .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) | 77 | ModuleDef::Trait(trait_) => Some(trait_), |
78 | .as_ref() | 78 | _ => None, |
79 | .map(mod_path_to_ast) | 79 | }) |
80 | .zip(Some(trait_)) | 80 | .flat_map(|trait_| { |
81 | }); | 81 | current_module |
82 | .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) | ||
83 | .as_ref() | ||
84 | .map(mod_path_to_ast) | ||
85 | .zip(Some(trait_)) | ||
86 | }); | ||
82 | 87 | ||
83 | let mut no_traits_found = true; | 88 | let mut no_traits_found = true; |
84 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { | 89 | 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 08df2df3f..eb2cba631 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs | |||
@@ -947,4 +947,36 @@ fn main() { | |||
947 | expect![[]], | 947 | expect![[]], |
948 | ) | 948 | ) |
949 | } | 949 | } |
950 | |||
951 | #[test] | ||
952 | fn unqualified_assoc_items_are_omitted() { | ||
953 | check( | ||
954 | r#" | ||
955 | mod something { | ||
956 | pub trait BaseTrait { | ||
957 | fn test_function() -> i32; | ||
958 | } | ||
959 | |||
960 | pub struct Item1; | ||
961 | pub struct Item2; | ||
962 | |||
963 | impl BaseTrait for Item1 { | ||
964 | fn test_function() -> i32 { | ||
965 | 1 | ||
966 | } | ||
967 | } | ||
968 | |||
969 | impl BaseTrait for Item2 { | ||
970 | fn test_function() -> i32 { | ||
971 | 2 | ||
972 | } | ||
973 | } | ||
974 | } | ||
975 | |||
976 | fn main() { | ||
977 | test_f$0 | ||
978 | }"#, | ||
979 | expect![[]], | ||
980 | ) | ||
981 | } | ||
950 | } | 982 | } |
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 995970fca..87cddb98e 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -14,7 +14,10 @@ mod completions; | |||
14 | use completions::flyimport::position_for_import; | 14 | use completions::flyimport::position_for_import; |
15 | use ide_db::{ | 15 | use ide_db::{ |
16 | base_db::FilePosition, | 16 | base_db::FilePosition, |
17 | helpers::{import_assets::LocatedImport, insert_use::ImportScope}, | 17 | helpers::{ |
18 | import_assets::{LocatedImport, NameToImport}, | ||
19 | insert_use::ImportScope, | ||
20 | }, | ||
18 | items_locator, RootDatabase, | 21 | items_locator, RootDatabase, |
19 | }; | 22 | }; |
20 | use text_edit::TextEdit; | 23 | use text_edit::TextEdit; |
@@ -151,15 +154,20 @@ pub fn resolve_completion_edits( | |||
151 | let current_module = ctx.sema.scope(position_for_import).module()?; | 154 | let current_module = ctx.sema.scope(position_for_import).module()?; |
152 | let current_crate = current_module.krate(); | 155 | let current_crate = current_module.krate(); |
153 | 156 | ||
154 | let (import_path, item_to_import) = | 157 | let (import_path, item_to_import) = items_locator::items_with_name( |
155 | items_locator::with_exact_name(&ctx.sema, current_crate, imported_name) | 158 | &ctx.sema, |
156 | .into_iter() | 159 | current_crate, |
157 | .filter_map(|candidate| { | 160 | NameToImport::Exact(imported_name), |
158 | current_module | 161 | items_locator::AssocItemSearch::Include, |
159 | .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind) | 162 | Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT), |
160 | .zip(Some(candidate)) | 163 | ) |
161 | }) | 164 | .into_iter() |
162 | .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?; | 165 | .filter_map(|candidate| { |
166 | current_module | ||
167 | .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind) | ||
168 | .zip(Some(candidate)) | ||
169 | }) | ||
170 | .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?; | ||
163 | let import = | 171 | let import = |
164 | LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path)); | 172 | LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path)); |
165 | 173 | ||
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs index 7c8844e95..0da7a1a9d 100644 --- a/crates/ide_db/src/helpers/import_assets.rs +++ b/crates/ide_db/src/helpers/import_assets.rs | |||
@@ -61,7 +61,7 @@ pub struct FirstSegmentUnresolved { | |||
61 | } | 61 | } |
62 | 62 | ||
63 | /// A name that will be used during item lookups. | 63 | /// A name that will be used during item lookups. |
64 | #[derive(Debug)] | 64 | #[derive(Debug, Clone)] |
65 | pub enum NameToImport { | 65 | pub enum NameToImport { |
66 | /// Requires items with names that exactly match the given string, case-sensitive. | 66 | /// Requires items with names that exactly match the given string, case-sensitive. |
67 | Exact(String), | 67 | Exact(String), |
@@ -201,129 +201,103 @@ impl ImportAssets { | |||
201 | sema: &Semantics<RootDatabase>, | 201 | sema: &Semantics<RootDatabase>, |
202 | prefixed: Option<PrefixKind>, | 202 | prefixed: Option<PrefixKind>, |
203 | ) -> Vec<LocatedImport> { | 203 | ) -> Vec<LocatedImport> { |
204 | let items_with_candidate_name = match self.name_to_import() { | 204 | let _p = profile::span("import_assets::search_for"); |
205 | NameToImport::Exact(exact_name) => items_locator::with_exact_name( | ||
206 | sema, | ||
207 | self.module_with_candidate.krate(), | ||
208 | exact_name.clone(), | ||
209 | ), | ||
210 | // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items: | ||
211 | // instead, we need to look up all trait impls for a certain struct and search through them only | ||
212 | // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032 | ||
213 | // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup | ||
214 | // for the details | ||
215 | NameToImport::Fuzzy(fuzzy_name) => { | ||
216 | let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() { | ||
217 | (AssocItemSearch::AssocItemsOnly, None) | ||
218 | } else { | ||
219 | (AssocItemSearch::Include, Some(DEFAULT_QUERY_SEARCH_LIMIT)) | ||
220 | }; | ||
221 | |||
222 | items_locator::with_similar_name( | ||
223 | sema, | ||
224 | self.module_with_candidate.krate(), | ||
225 | fuzzy_name.clone(), | ||
226 | assoc_item_search, | ||
227 | limit, | ||
228 | ) | ||
229 | } | ||
230 | }; | ||
231 | 205 | ||
232 | let scope_definitions = self.scope_definitions(sema); | 206 | let scope_definitions = self.scope_definitions(sema); |
233 | self.applicable_defs(sema.db, prefixed, items_with_candidate_name) | ||
234 | .into_iter() | ||
235 | .filter(|import| import.import_path.len() > 1) | ||
236 | .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import))) | ||
237 | .sorted_by_key(|import| import.import_path.clone()) | ||
238 | .collect() | ||
239 | } | ||
240 | |||
241 | fn scope_definitions(&self, sema: &Semantics<RootDatabase>) -> FxHashSet<ScopeDef> { | ||
242 | let mut scope_definitions = FxHashSet::default(); | ||
243 | sema.scope(&self.candidate_node).process_all_names(&mut |_, scope_def| { | ||
244 | scope_definitions.insert(scope_def); | ||
245 | }); | ||
246 | scope_definitions | ||
247 | } | ||
248 | |||
249 | fn name_to_import(&self) -> &NameToImport { | ||
250 | match &self.import_candidate { | ||
251 | ImportCandidate::Path(candidate) => &candidate.name, | ||
252 | ImportCandidate::TraitAssocItem(candidate) | ||
253 | | ImportCandidate::TraitMethod(candidate) => &candidate.assoc_item_name, | ||
254 | } | ||
255 | } | ||
256 | |||
257 | fn applicable_defs( | ||
258 | &self, | ||
259 | db: &RootDatabase, | ||
260 | prefixed: Option<PrefixKind>, | ||
261 | items_with_candidate_name: FxHashSet<ItemInNs>, | ||
262 | ) -> FxHashSet<LocatedImport> { | ||
263 | let _p = profile::span("import_assets::applicable_defs"); | ||
264 | let current_crate = self.module_with_candidate.krate(); | 207 | let current_crate = self.module_with_candidate.krate(); |
265 | |||
266 | let mod_path = |item| { | 208 | let mod_path = |item| { |
267 | get_mod_path(db, item_for_path_search(db, item)?, &self.module_with_candidate, prefixed) | 209 | get_mod_path( |
210 | sema.db, | ||
211 | item_for_path_search(sema.db, item)?, | ||
212 | &self.module_with_candidate, | ||
213 | prefixed, | ||
214 | ) | ||
268 | }; | 215 | }; |
269 | 216 | ||
270 | match &self.import_candidate { | 217 | match &self.import_candidate { |
271 | ImportCandidate::Path(path_candidate) => { | 218 | ImportCandidate::Path(path_candidate) => { |
272 | path_applicable_imports(db, path_candidate, mod_path, items_with_candidate_name) | 219 | path_applicable_imports(sema, current_crate, path_candidate, mod_path) |
220 | } | ||
221 | ImportCandidate::TraitAssocItem(trait_candidate) => { | ||
222 | trait_applicable_items(sema, current_crate, trait_candidate, true, mod_path) | ||
223 | } | ||
224 | ImportCandidate::TraitMethod(trait_candidate) => { | ||
225 | trait_applicable_items(sema, current_crate, trait_candidate, false, mod_path) | ||
273 | } | 226 | } |
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 | } | 227 | } |
228 | .into_iter() | ||
229 | .filter(|import| import.import_path.len() > 1) | ||
230 | .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import))) | ||
231 | .sorted_by_key(|import| import.import_path.clone()) | ||
232 | .collect() | ||
233 | } | ||
234 | |||
235 | fn scope_definitions(&self, sema: &Semantics<RootDatabase>) -> FxHashSet<ScopeDef> { | ||
236 | let _p = profile::span("import_assets::scope_definitions"); | ||
237 | let mut scope_definitions = FxHashSet::default(); | ||
238 | sema.scope(&self.candidate_node).process_all_names(&mut |_, scope_def| { | ||
239 | scope_definitions.insert(scope_def); | ||
240 | }); | ||
241 | scope_definitions | ||
291 | } | 242 | } |
292 | } | 243 | } |
293 | 244 | ||
294 | fn path_applicable_imports( | 245 | fn path_applicable_imports( |
295 | db: &RootDatabase, | 246 | sema: &Semantics<RootDatabase>, |
247 | current_crate: Crate, | ||
296 | path_candidate: &PathImportCandidate, | 248 | path_candidate: &PathImportCandidate, |
297 | mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy, | 249 | mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy, |
298 | items_with_candidate_name: FxHashSet<ItemInNs>, | ||
299 | ) -> FxHashSet<LocatedImport> { | 250 | ) -> FxHashSet<LocatedImport> { |
300 | let _p = profile::span("import_assets::path_applicable_imports"); | 251 | let _p = profile::span("import_assets::path_applicable_imports"); |
301 | 252 | ||
302 | let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier { | 253 | match &path_candidate.qualifier { |
303 | None => { | 254 | None => { |
304 | return items_with_candidate_name | 255 | items_locator::items_with_name( |
305 | .into_iter() | 256 | sema, |
306 | .filter_map(|item| { | 257 | current_crate, |
307 | let mut mod_path = mod_path(item)?; | 258 | path_candidate.name.clone(), |
308 | if let Some(assoc_item) = item_as_assoc(db, item) { | 259 | // FIXME: we could look up assoc items by the input and propose those in completion, |
309 | mod_path.push_segment(assoc_item.name(db)?); | 260 | // but that requries more preparation first: |
310 | } | 261 | // * store non-trait assoc items in import_map to fully enable this lookup |
311 | Some(LocatedImport::new(mod_path.clone(), item, item, Some(mod_path))) | 262 | // * ensure that does not degrade the performance (bencmark it) |
312 | }) | 263 | // * write more logic to check for corresponding trait presence requirement (we're unable to flyimport multiple item right now) |
313 | .collect(); | 264 | // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear |
265 | // | ||
266 | // see also an ignored test under FIXME comment in the qualify_path.rs module | ||
267 | AssocItemSearch::Exclude, | ||
268 | Some(DEFAULT_QUERY_SEARCH_LIMIT), | ||
269 | ) | ||
270 | .into_iter() | ||
271 | .filter_map(|item| { | ||
272 | let mod_path = mod_path(item)?; | ||
273 | Some(LocatedImport::new(mod_path.clone(), item, item, Some(mod_path))) | ||
274 | }) | ||
275 | .collect() | ||
314 | } | 276 | } |
315 | Some(first_segment_unresolved) => ( | 277 | Some(first_segment_unresolved) => { |
316 | first_segment_unresolved.fist_segment.to_string(), | 278 | let unresolved_qualifier = |
317 | path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier), | 279 | path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier); |
318 | ), | 280 | let unresolved_first_segment = first_segment_unresolved.fist_segment.text(); |
319 | }; | 281 | items_locator::items_with_name( |
320 | 282 | sema, | |
321 | items_with_candidate_name | 283 | current_crate, |
322 | .into_iter() | 284 | path_candidate.name.clone(), |
323 | .filter_map(|item| { | 285 | AssocItemSearch::Include, |
324 | import_for_item(db, mod_path, &unresolved_first_segment, &unresolved_qualifier, item) | 286 | Some(DEFAULT_QUERY_SEARCH_LIMIT), |
325 | }) | 287 | ) |
326 | .collect() | 288 | .into_iter() |
289 | .filter_map(|item| { | ||
290 | import_for_item( | ||
291 | sema.db, | ||
292 | mod_path, | ||
293 | unresolved_first_segment, | ||
294 | &unresolved_qualifier, | ||
295 | item, | ||
296 | ) | ||
297 | }) | ||
298 | .collect() | ||
299 | } | ||
300 | } | ||
327 | } | 301 | } |
328 | 302 | ||
329 | fn import_for_item( | 303 | fn import_for_item( |
@@ -438,25 +412,32 @@ fn module_with_segment_name( | |||
438 | } | 412 | } |
439 | 413 | ||
440 | fn trait_applicable_items( | 414 | fn trait_applicable_items( |
441 | db: &RootDatabase, | 415 | sema: &Semantics<RootDatabase>, |
442 | current_crate: Crate, | 416 | current_crate: Crate, |
443 | trait_candidate: &TraitImportCandidate, | 417 | trait_candidate: &TraitImportCandidate, |
444 | trait_assoc_item: bool, | 418 | trait_assoc_item: bool, |
445 | mod_path: impl Fn(ItemInNs) -> Option<ModPath>, | 419 | mod_path: impl Fn(ItemInNs) -> Option<ModPath>, |
446 | items_with_candidate_name: FxHashSet<ItemInNs>, | ||
447 | ) -> FxHashSet<LocatedImport> { | 420 | ) -> FxHashSet<LocatedImport> { |
448 | let _p = profile::span("import_assets::trait_applicable_items"); | 421 | let _p = profile::span("import_assets::trait_applicable_items"); |
449 | let mut required_assoc_items = FxHashSet::default(); | ||
450 | 422 | ||
451 | let trait_candidates = items_with_candidate_name | 423 | let db = sema.db; |
452 | .into_iter() | 424 | |
453 | .filter_map(|input| item_as_assoc(db, input)) | 425 | let mut required_assoc_items = FxHashSet::default(); |
454 | .filter_map(|assoc| { | 426 | let trait_candidates = items_locator::items_with_name( |
455 | let assoc_item_trait = assoc.containing_trait(db)?; | 427 | sema, |
456 | required_assoc_items.insert(assoc); | 428 | current_crate, |
457 | Some(assoc_item_trait.into()) | 429 | trait_candidate.assoc_item_name.clone(), |
458 | }) | 430 | AssocItemSearch::AssocItemsOnly, |
459 | .collect(); | 431 | Some(DEFAULT_QUERY_SEARCH_LIMIT), |
432 | ) | ||
433 | .into_iter() | ||
434 | .filter_map(|input| item_as_assoc(db, input)) | ||
435 | .filter_map(|assoc| { | ||
436 | let assoc_item_trait = assoc.containing_trait(db)?; | ||
437 | required_assoc_items.insert(assoc); | ||
438 | Some(assoc_item_trait.into()) | ||
439 | }) | ||
440 | .collect(); | ||
460 | 441 | ||
461 | let mut located_imports = FxHashSet::default(); | 442 | let mut located_imports = FxHashSet::default(); |
462 | 443 | ||
@@ -565,10 +546,6 @@ impl ImportCandidate { | |||
565 | ) -> Option<Self> { | 546 | ) -> Option<Self> { |
566 | path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name)) | 547 | path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name)) |
567 | } | 548 | } |
568 | |||
569 | fn is_trait_candidate(&self) -> bool { | ||
570 | matches!(self, ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_)) | ||
571 | } | ||
572 | } | 549 | } |
573 | 550 | ||
574 | fn path_import_candidate( | 551 | fn path_import_candidate( |
diff --git a/crates/ide_db/src/items_locator.rs b/crates/ide_db/src/items_locator.rs index 8a7f02935..518cddd74 100644 --- a/crates/ide_db/src/items_locator.rs +++ b/crates/ide_db/src/items_locator.rs | |||
@@ -1,6 +1,7 @@ | |||
1 | //! This module contains an import search functionality that is provided to the assists module. | 1 | //! This module has the functionality to search the project and its dependencies for a certain item, |
2 | //! Later, this should be moved away to a separate crate that is accessible from the assists module. | 2 | //! by its name and a few criteria. |
3 | 3 | //! The main reason for this module to exist is the fact that project's items and dependencies' items | |
4 | //! are located in different caches, with different APIs. | ||
4 | use either::Either; | 5 | use either::Either; |
5 | use hir::{ | 6 | use hir::{ |
6 | import_map::{self, ImportKind}, | 7 | import_map::{self, ImportKind}, |
@@ -10,122 +11,118 @@ use syntax::{ast, AstNode, SyntaxKind::NAME}; | |||
10 | 11 | ||
11 | use crate::{ | 12 | use crate::{ |
12 | defs::{Definition, NameClass}, | 13 | defs::{Definition, NameClass}, |
14 | helpers::import_assets::NameToImport, | ||
13 | symbol_index::{self, FileSymbol}, | 15 | symbol_index::{self, FileSymbol}, |
14 | RootDatabase, | 16 | RootDatabase, |
15 | }; | 17 | }; |
16 | use rustc_hash::FxHashSet; | 18 | use rustc_hash::FxHashSet; |
17 | 19 | ||
18 | pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40; | 20 | /// A value to use, when uncertain which limit to pick. |
21 | pub const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40; | ||
19 | 22 | ||
20 | pub fn with_exact_name( | 23 | /// Three possible ways to search for the name in associated and/or other items. |
21 | sema: &Semantics<'_, RootDatabase>, | 24 | #[derive(Debug, Clone, Copy)] |
22 | krate: Crate, | ||
23 | exact_name: String, | ||
24 | ) -> FxHashSet<ItemInNs> { | ||
25 | let _p = profile::span("find_exact_imports"); | ||
26 | find_items( | ||
27 | sema, | ||
28 | krate, | ||
29 | { | ||
30 | let mut local_query = symbol_index::Query::new(exact_name.clone()); | ||
31 | local_query.exact(); | ||
32 | local_query.limit(DEFAULT_QUERY_SEARCH_LIMIT); | ||
33 | local_query | ||
34 | }, | ||
35 | import_map::Query::new(exact_name) | ||
36 | .limit(DEFAULT_QUERY_SEARCH_LIMIT) | ||
37 | .name_only() | ||
38 | .search_mode(import_map::SearchMode::Equals) | ||
39 | .case_sensitive(), | ||
40 | ) | ||
41 | } | ||
42 | |||
43 | #[derive(Debug)] | ||
44 | pub enum AssocItemSearch { | 25 | pub enum AssocItemSearch { |
26 | /// Search for the name in both associated and other items. | ||
45 | Include, | 27 | Include, |
28 | /// Search for the name in other items only. | ||
46 | Exclude, | 29 | Exclude, |
30 | /// Search for the name in the associated items only. | ||
47 | AssocItemsOnly, | 31 | AssocItemsOnly, |
48 | } | 32 | } |
49 | 33 | ||
50 | pub fn with_similar_name( | 34 | /// Searches for importable items with the given name in the crate and its dependencies. |
35 | pub fn items_with_name( | ||
51 | sema: &Semantics<'_, RootDatabase>, | 36 | sema: &Semantics<'_, RootDatabase>, |
52 | krate: Crate, | 37 | krate: Crate, |
53 | fuzzy_search_string: String, | 38 | name: NameToImport, |
54 | assoc_item_search: AssocItemSearch, | 39 | assoc_item_search: AssocItemSearch, |
55 | limit: Option<usize>, | 40 | limit: Option<usize>, |
56 | ) -> FxHashSet<ItemInNs> { | 41 | ) -> FxHashSet<ItemInNs> { |
57 | let _p = profile::span("find_similar_imports"); | 42 | let _p = profile::span("items_with_name").detail(|| { |
43 | format!( | ||
44 | "Name: {} ({:?}), crate: {:?}, limit: {:?}", | ||
45 | name.text(), | ||
46 | assoc_item_search, | ||
47 | krate.display_name(sema.db).map(|name| name.to_string()), | ||
48 | limit, | ||
49 | ) | ||
50 | }); | ||
51 | |||
52 | let (mut local_query, mut external_query) = match name { | ||
53 | NameToImport::Exact(exact_name) => { | ||
54 | let mut local_query = symbol_index::Query::new(exact_name.clone()); | ||
55 | local_query.exact(); | ||
58 | 56 | ||
59 | let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) | 57 | let external_query = import_map::Query::new(exact_name) |
60 | .search_mode(import_map::SearchMode::Fuzzy) | 58 | .name_only() |
61 | .name_only(); | 59 | .search_mode(import_map::SearchMode::Equals) |
60 | .case_sensitive(); | ||
62 | 61 | ||
63 | match assoc_item_search { | 62 | (local_query, external_query) |
64 | AssocItemSearch::Include => {} | ||
65 | AssocItemSearch::Exclude => { | ||
66 | external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem); | ||
67 | } | 63 | } |
68 | AssocItemSearch::AssocItemsOnly => { | 64 | NameToImport::Fuzzy(fuzzy_search_string) => { |
69 | external_query = external_query.assoc_items_only(); | 65 | let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) |
66 | .search_mode(import_map::SearchMode::Fuzzy) | ||
67 | .name_only(); | ||
68 | match assoc_item_search { | ||
69 | AssocItemSearch::Include => {} | ||
70 | AssocItemSearch::Exclude => { | ||
71 | external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem); | ||
72 | } | ||
73 | AssocItemSearch::AssocItemsOnly => { | ||
74 | external_query = external_query.assoc_items_only(); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | (symbol_index::Query::new(fuzzy_search_string), external_query) | ||
70 | } | 79 | } |
71 | } | 80 | }; |
72 | |||
73 | let mut local_query = symbol_index::Query::new(fuzzy_search_string); | ||
74 | 81 | ||
75 | if let Some(limit) = limit { | 82 | if let Some(limit) = limit { |
76 | external_query = external_query.limit(limit); | 83 | external_query = external_query.limit(limit); |
77 | local_query.limit(limit); | 84 | local_query.limit(limit); |
78 | } | 85 | } |
79 | 86 | ||
80 | find_items(sema, krate, local_query, external_query) | 87 | find_items(sema, krate, assoc_item_search, local_query, external_query) |
81 | .into_iter() | ||
82 | .filter(move |&item| match assoc_item_search { | ||
83 | AssocItemSearch::Include => true, | ||
84 | AssocItemSearch::Exclude => !is_assoc_item(item, sema.db), | ||
85 | AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db), | ||
86 | }) | ||
87 | .collect() | ||
88 | } | ||
89 | |||
90 | fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool { | ||
91 | item.as_module_def_id() | ||
92 | .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db)) | ||
93 | .is_some() | ||
94 | } | 88 | } |
95 | 89 | ||
96 | fn find_items( | 90 | fn find_items( |
97 | sema: &Semantics<'_, RootDatabase>, | 91 | sema: &Semantics<'_, RootDatabase>, |
98 | krate: Crate, | 92 | krate: Crate, |
93 | assoc_item_search: AssocItemSearch, | ||
99 | local_query: symbol_index::Query, | 94 | local_query: symbol_index::Query, |
100 | external_query: import_map::Query, | 95 | external_query: import_map::Query, |
101 | ) -> FxHashSet<ItemInNs> { | 96 | ) -> FxHashSet<ItemInNs> { |
102 | let _p = profile::span("find_similar_imports"); | 97 | let _p = profile::span("find_items"); |
103 | let db = sema.db; | 98 | let db = sema.db; |
104 | 99 | ||
105 | // Query dependencies first. | 100 | let external_importables = |
106 | let mut candidates = krate | 101 | krate.query_external_importables(db, external_query).map(|external_importable| { |
107 | .query_external_importables(db, external_query) | 102 | match external_importable { |
108 | .map(|external_importable| match external_importable { | 103 | Either::Left(module_def) => ItemInNs::from(module_def), |
109 | Either::Left(module_def) => ItemInNs::from(module_def), | 104 | Either::Right(macro_def) => ItemInNs::from(macro_def), |
110 | Either::Right(macro_def) => ItemInNs::from(macro_def), | 105 | } |
111 | }) | 106 | }); |
112 | .collect::<FxHashSet<_>>(); | ||
113 | 107 | ||
114 | // Query the local crate using the symbol index. | 108 | // Query the local crate using the symbol index. |
115 | let local_results = symbol_index::crate_symbols(db, krate.into(), local_query); | 109 | let local_results = symbol_index::crate_symbols(db, krate.into(), local_query) |
116 | 110 | .into_iter() | |
117 | candidates.extend( | 111 | .filter_map(|local_candidate| get_name_definition(sema, &local_candidate)) |
118 | local_results | 112 | .filter_map(|name_definition_to_import| match name_definition_to_import { |
119 | .into_iter() | 113 | Definition::ModuleDef(module_def) => Some(ItemInNs::from(module_def)), |
120 | .filter_map(|local_candidate| get_name_definition(sema, &local_candidate)) | 114 | Definition::Macro(macro_def) => Some(ItemInNs::from(macro_def)), |
121 | .filter_map(|name_definition_to_import| match name_definition_to_import { | 115 | _ => None, |
122 | Definition::ModuleDef(module_def) => Some(ItemInNs::from(module_def)), | 116 | }); |
123 | Definition::Macro(macro_def) => Some(ItemInNs::from(macro_def)), | 117 | |
124 | _ => None, | 118 | external_importables |
125 | }), | 119 | .chain(local_results) |
126 | ); | 120 | .filter(move |&item| match assoc_item_search { |
127 | 121 | AssocItemSearch::Include => true, | |
128 | candidates | 122 | AssocItemSearch::Exclude => !is_assoc_item(item, sema.db), |
123 | AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db), | ||
124 | }) | ||
125 | .collect() | ||
129 | } | 126 | } |
130 | 127 | ||
131 | fn get_name_definition( | 128 | fn get_name_definition( |
@@ -144,3 +141,9 @@ fn get_name_definition( | |||
144 | let name = ast::Name::cast(candidate_name_node)?; | 141 | let name = ast::Name::cast(candidate_name_node)?; |
145 | NameClass::classify(sema, &name)?.defined(sema.db) | 142 | NameClass::classify(sema, &name)?.defined(sema.db) |
146 | } | 143 | } |
144 | |||
145 | fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool { | ||
146 | item.as_module_def_id() | ||
147 | .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db)) | ||
148 | .is_some() | ||
149 | } | ||