aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-03-21 09:37:08 +0000
committerGitHub <[email protected]>2021-03-21 09:37:08 +0000
commit09412d85fc3137d6ada3b27170e14c287f1a1191 (patch)
treeac01ec54df0cebba975c82008482319820ce8757 /crates/ide_db
parent2280f62a40f31d83fd79b62c46dd3d610354d78c (diff)
parent56a7d246d59d9429304b82bce2f1e71b632c5737 (diff)
Merge #8123
8123: Do not display unqualified assoc item completions r=SomeoneToIgnore a=SomeoneToIgnore Part of https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/autoimport.20weirdness Removes all flyimport completions for any unqualified associated type, effectively reverting https://github.com/rust-analyzer/rust-analyzer/pull/8095 I've explained the reasoning in the corresponding FIXME and open to discussions. As an alternative way, we could add yet another parameter in the method that's used by the `qualify_path` and enable it for the qualify assists only. Co-authored-by: Kirill Bulatov <[email protected]>
Diffstat (limited to 'crates/ide_db')
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs215
-rw-r--r--crates/ide_db/src/items_locator.rs163
2 files changed, 179 insertions, 199 deletions
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)]
65pub enum NameToImport { 65pub 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
294fn path_applicable_imports( 245fn 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
329fn import_for_item( 303fn import_for_item(
@@ -438,25 +412,32 @@ fn module_with_segment_name(
438} 412}
439 413
440fn trait_applicable_items( 414fn 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
574fn path_import_candidate( 551fn 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.
4use either::Either; 5use either::Either;
5use hir::{ 6use hir::{
6 import_map::{self, ImportKind}, 7 import_map::{self, ImportKind},
@@ -10,122 +11,118 @@ use syntax::{ast, AstNode, SyntaxKind::NAME};
10 11
11use crate::{ 12use 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};
16use rustc_hash::FxHashSet; 18use rustc_hash::FxHashSet;
17 19
18pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40; 20/// A value to use, when uncertain which limit to pick.
21pub const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40;
19 22
20pub 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)]
44pub enum AssocItemSearch { 25pub 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
50pub fn with_similar_name( 34/// Searches for importable items with the given name in the crate and its dependencies.
35pub 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
90fn 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
96fn find_items( 90fn 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
131fn get_name_definition( 128fn 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
145fn 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}