From 2339ba4440bc8b23f3705efb57a439b4ec8d3505 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 11 Nov 2023 14:04:24 +0100 Subject: [PATCH 1/4] Prepare ImportMap for supportin multiple import paths per item --- crates/hir-def/src/find_path.rs | 35 ++++---- crates/hir-def/src/import_map.rs | 132 ++++++++++++++++++++----------- 2 files changed, 105 insertions(+), 62 deletions(-) diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs index b9c5ff7279..2a0a450097 100644 --- a/crates/hir-def/src/find_path.rs +++ b/crates/hir-def/src/find_path.rs @@ -367,18 +367,18 @@ fn calculate_best_path( // too (unless we can't name it at all). It could *also* be (re)exported by the same crate // that wants to import it here, but we always prefer to use the external path here. - let crate_graph = db.crate_graph(); - let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| { + for dep in &db.crate_graph()[from.krate].dependencies { let import_map = db.import_map(dep.crate_id); - import_map.import_info_for(item).and_then(|info| { + let Some(import_info_for) = import_map.import_info_for(item) else { continue }; + for info in import_info_for { if info.is_doc_hidden { // the item or import is `#[doc(hidden)]`, so skip it as it is in an external crate - return None; + continue; } // Determine best path for containing module and append last segment from `info`. // FIXME: we should guide this to look up the path locally, or from the same crate again? - let (mut path, path_stability) = find_path_for_module( + let Some((mut path, path_stability)) = find_path_for_module( db, def_map, visited_modules, @@ -388,22 +388,23 @@ fn calculate_best_path( max_len - 1, prefixed, prefer_no_std, - )?; + ) else { + continue; + }; cov_mark::hit!(partially_imported); path.push_segment(info.name.clone()); - Some(( + + let path_with_stab = ( path, zip_stability(path_stability, if info.is_unstable { Unstable } else { Stable }), - )) - }) - }); + ); - for path in extern_paths { - let new_path = match best_path.take() { - Some(best_path) => select_best_path(best_path, path, prefer_no_std), - None => path, - }; - update_best_path(&mut best_path, new_path); + let new_path_with_stab = match best_path.take() { + Some(best_path) => select_best_path(best_path, path_with_stab, prefer_no_std), + None => path_with_stab, + }; + update_best_path(&mut best_path, new_path_with_stab); + } } } if let Some(module) = item.module(db) { @@ -593,7 +594,7 @@ mod tests { let found_path = find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, false); - assert_eq!(found_path, Some(mod_path), "{prefix_kind:?}"); + assert_eq!(found_path, Some(mod_path), "on kind: {prefix_kind:?}"); } fn check_found_path( diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index 6461439bb7..d3dee8e7c7 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -3,11 +3,12 @@ use std::{collections::hash_map::Entry, fmt, hash::BuildHasherDefault}; use base_db::CrateId; -use fst::{self, Streamer}; +use fst::{self, raw::IndexedValue, Streamer}; use hir_expand::name::Name; use indexmap::IndexMap; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; +use smallvec::{smallvec, SmallVec}; use triomphe::Arc; use crate::{ @@ -20,8 +21,6 @@ use crate::{ type FxIndexMap = IndexMap>; -// FIXME: Support aliases: an item may be exported under multiple names, so `ImportInfo` should -// have `Vec<(Name, ModuleId)>` instead of `(Name, ModuleId)`. /// Item import details stored in the `ImportMap`. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ImportInfo { @@ -29,22 +28,21 @@ pub struct ImportInfo { pub name: Name, /// The module containing this item. pub container: ModuleId, - /// Whether the import is a trait associated item or not. - pub is_trait_assoc_item: bool, /// Whether this item is annotated with `#[doc(hidden)]`. pub is_doc_hidden: bool, /// Whether this item is annotated with `#[unstable(..)]`. pub is_unstable: bool, } +type ImportMapIndex = FxIndexMap, IsTraitAssocItem)>; + /// A map from publicly exported items to its name. /// /// Reexports of items are taken into account, ie. if something is exported under multiple /// names, the one with the shortest import path will be used. #[derive(Default)] pub struct ImportMap { - map: FxIndexMap, - + map: ImportMapIndex, /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the /// values returned by running `fst`. /// @@ -55,6 +53,12 @@ pub struct ImportMap { fst: fst::Map>, } +#[derive(Copy, Clone, PartialEq, Eq)] +enum IsTraitAssocItem { + Yes, + No, +} + impl ImportMap { pub(crate) fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc { let _p = profile::span("import_map_query"); @@ -64,9 +68,13 @@ impl ImportMap { let mut importables: Vec<_> = map .iter() // We've only collected items, whose name cannot be tuple field. - .map(|(&item, info)| (item, info.name.as_str().unwrap().to_ascii_lowercase())) + .flat_map(|(&item, (info, _))| { + info.iter() + .map(move |info| (item, info.name.as_str().unwrap().to_ascii_lowercase())) + }) .collect(); importables.sort_by(|(_, lhs_name), (_, rhs_name)| lhs_name.cmp(rhs_name)); + importables.dedup(); // Build the FST, taking care not to insert duplicate values. let mut builder = fst::MapBuilder::memory(); @@ -82,12 +90,12 @@ impl ImportMap { }) } - pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { - self.map.get(&item) + pub fn import_info_for(&self, item: ItemInNs) -> Option<&[ImportInfo]> { + self.map.get(&item).map(|(info, _)| &**info) } } -fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap { +fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex { let _p = profile::span("collect_import_map"); let def_map = db.crate_def_map(krate); @@ -140,7 +148,6 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap FxIndexMap FxIndexMap FxIndexMap, + map: &mut ImportMapIndex, tr: TraitId, is_type_in_ns: bool, trait_import_info: &ImportInfo, @@ -241,11 +250,10 @@ fn collect_trait_assoc_items( let assoc_item_info = ImportInfo { container: trait_import_info.container, name: assoc_item_name.clone(), - is_trait_assoc_item: true, is_doc_hidden: attrs.has_doc_hidden(), is_unstable: attrs.is_unstable(), }; - map.insert(assoc_item, assoc_item_info); + map.insert(assoc_item, (smallvec![assoc_item_info], IsTraitAssocItem::Yes)); } } @@ -349,6 +357,15 @@ impl Query { Self { case_sensitive: true, ..self } } + fn matches_assoc_mode(&self, is_trait_assoc_item: IsTraitAssocItem) -> bool { + match (is_trait_assoc_item, self.assoc_mode) { + (IsTraitAssocItem::Yes, AssocSearchMode::Exclude) + | (IsTraitAssocItem::No, AssocSearchMode::AssocItemsOnly) => false, + _ => true, + } + } + + /// Checks whether the import map entry matches the query. fn import_matches( &self, db: &dyn DefDatabase, @@ -356,12 +373,8 @@ impl Query { enforce_lowercase: bool, ) -> bool { let _p = profile::span("import_map::Query::import_matches"); - match (import.is_trait_assoc_item, self.assoc_mode) { - (true, AssocSearchMode::Exclude) => return false, - (false, AssocSearchMode::AssocItemsOnly) => return false, - _ => {} - } + // FIXME: Can we get rid of the alloc here? let mut input = import.name.display(db.upcast()).to_string(); let case_insensitive = enforce_lowercase || !self.case_sensitive; if case_insensitive { @@ -411,32 +424,55 @@ pub fn search_dependencies( let mut res = FxHashSet::default(); while let Some((_, indexed_values)) = stream.next() { - for indexed_value in indexed_values { - let import_map = &import_maps[indexed_value.index]; - let importables = &import_map.importables[indexed_value.value as usize..]; + for &IndexedValue { index, value } in indexed_values { + let import_map = &import_maps[index]; + let [importable, importables @ ..] = &import_map.importables[value as usize..] else { + continue; + }; - let common_importable_data = &import_map.map[&importables[0]]; - if !query.import_matches(db, common_importable_data, true) { + let &(ref importable_data, is_trait_assoc_item) = &import_map.map[importable]; + if !query.matches_assoc_mode(is_trait_assoc_item) { continue; } - // Name shared by the importable items in this group. - let common_importable_name = - common_importable_data.name.to_smol_str().to_ascii_lowercase(); - // Add the items from this name group. Those are all subsequent items in - // `importables` whose name match `common_importable_name`. - let iter = importables - .iter() - .copied() - .take_while(|item| { - common_importable_name - == import_map.map[item].name.to_smol_str().to_ascii_lowercase() - }) - .filter(|item| { - !query.case_sensitive // we've already checked the common importables name case-insensitively - || query.import_matches(db, &import_map.map[item], false) - }); - res.extend(iter); + // FIXME: We probably need to account for other possible matches in this alias group? + let Some(common_importable_data) = + importable_data.iter().find(|&info| query.import_matches(db, info, true)) + else { + continue; + }; + res.insert(*importable); + + if !importables.is_empty() { + // FIXME: so many allocs... + // Name shared by the importable items in this group. + let common_importable_name = + common_importable_data.name.to_smol_str().to_ascii_lowercase(); + // Add the items from this name group. Those are all subsequent items in + // `importables` whose name match `common_importable_name`. + let iter = importables + .iter() + .copied() + .take_while(|item| { + let &(ref import_infos, assoc_mode) = &import_map.map[item]; + query.matches_assoc_mode(assoc_mode) + && import_infos.iter().any(|info| { + info.name.to_smol_str().to_ascii_lowercase() + == common_importable_name + }) + }) + .filter(|item| { + !query.case_sensitive || { + // we've already checked the common importables name case-insensitively + let &(ref import_infos, assoc_mode) = &import_map.map[item]; + query.matches_assoc_mode(assoc_mode) + && import_infos + .iter() + .any(|info| query.import_matches(db, info, false)) + } + }); + res.extend(iter); + } if res.len() >= query.limit { return res; @@ -461,6 +497,7 @@ mod tests { let mut importable_paths: Vec<_> = self .map .iter() + .flat_map(|(item, (info, _))| info.iter().map(move |info| (item, info))) .map(|(item, info)| { let path = render_path(db, info); let ns = match item { @@ -499,7 +536,7 @@ mod tests { let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) { Some(assoc_item_path) => (assoc_item_path, "a"), None => ( - render_path(&db, dependency_imports.import_info_for(dependency)?), + render_path(&db, &dependency_imports.import_info_for(dependency)?[0]), match dependency { ItemInNs::Types(ModuleDefId::FunctionId(_)) | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f", @@ -547,7 +584,12 @@ mod tests { .items .iter() .find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?; - Some(format!("{}::{}", render_path(db, trait_info), assoc_item_name.display(db.upcast()))) + // FIXME: This should check all import infos, not just the first + Some(format!( + "{}::{}", + render_path(db, &trait_info[0]), + assoc_item_name.display(db.upcast()) + )) } fn check(ra_fixture: &str, expect: Expect) { From 801a887954fd985901c326ba25bc39256f2477e7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 11 Nov 2023 14:48:44 +0100 Subject: [PATCH 2/4] Record all import paths per item in ImportMap --- crates/hir-def/src/import_map.rs | 147 ++++++++++++------------------- 1 file changed, 54 insertions(+), 93 deletions(-) diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index d3dee8e7c7..b857392f37 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -1,14 +1,14 @@ //! A map of all publicly exported items in a crate. -use std::{collections::hash_map::Entry, fmt, hash::BuildHasherDefault}; +use std::{fmt, hash::BuildHasherDefault}; use base_db::CrateId; use fst::{self, raw::IndexedValue, Streamer}; use hir_expand::name::Name; use indexmap::IndexMap; use itertools::Itertools; -use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; -use smallvec::{smallvec, SmallVec}; +use rustc_hash::{FxHashSet, FxHasher}; +use smallvec::SmallVec; use triomphe::Arc; use crate::{ @@ -103,11 +103,13 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex { // We look only into modules that are public(ly reexported), starting with the crate root. let root = def_map.module_id(DefMap::ROOT); - let mut worklist = vec![(root, 0u32)]; - // Records items' minimum module depth. - let mut depth_map = FxHashMap::default(); + let mut worklist = vec![root]; + let mut visited = FxHashSet::default(); - while let Some((module, depth)) = worklist.pop() { + while let Some(module) = worklist.pop() { + if !visited.insert(module) { + continue; + } let ext_def_map; let mod_data = if module.krate == krate { &def_map[module.local_id] @@ -126,6 +128,7 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex { } }); + // FIXME: This loop might add the same entry up to 3 times per item! dedup for (name, per_ns) in visible_items { for (item, import) in per_ns.iter_items() { let attr_id = if let Some(import) = import { @@ -139,11 +142,10 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex { ItemInNs::Macros(id) => Some(id.into()), } }; - let status @ (is_doc_hidden, is_unstable) = - attr_id.map_or((false, false), |attr_id| { - let attrs = db.attrs(attr_id); - (attrs.has_doc_hidden(), attrs.is_unstable()) - }); + let (is_doc_hidden, is_unstable) = attr_id.map_or((false, false), |attr_id| { + let attrs = db.attrs(attr_id); + (attrs.has_doc_hidden(), attrs.is_unstable()) + }); let import_info = ImportInfo { name: name.clone(), @@ -152,50 +154,6 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex { is_unstable, }; - match depth_map.entry(item) { - Entry::Vacant(entry) => _ = entry.insert((depth, status)), - Entry::Occupied(mut entry) => { - let &(occ_depth, (occ_is_doc_hidden, occ_is_unstable)) = entry.get(); - (depth, occ_depth); - let overwrite = match ( - is_doc_hidden, - occ_is_doc_hidden, - is_unstable, - occ_is_unstable, - ) { - // no change of hiddeness or unstableness - (true, true, true, true) - | (true, true, false, false) - | (false, false, true, true) - | (false, false, false, false) => depth < occ_depth, - - // either less hidden or less unstable, accept - (true, true, false, true) - | (false, true, true, true) - | (false, true, false, true) - | (false, true, false, false) - | (false, false, false, true) => true, - // more hidden or unstable, discard - (true, true, true, false) - | (true, false, true, true) - | (true, false, true, false) - | (true, false, false, false) - | (false, false, true, false) => false, - - // exchanges doc(hidden) for unstable (and vice-versa), - (true, false, false, true) | (false, true, true, false) => { - depth < occ_depth - } - }; - // FIXME: Remove the overwrite rules as we can now record exports and - // aliases for the same item - if !overwrite { - continue; - } - entry.insert((depth, status)); - } - } - if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() { collect_trait_assoc_items( db, @@ -206,13 +164,14 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex { ); } - map.insert(item, (smallvec![import_info], IsTraitAssocItem::No)); + map.entry(item) + .or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::No)) + .0 + .push(import_info); - // If we've just added a module, descend into it. We might traverse modules - // multiple times, but only if the module depth is smaller (else we `continue` - // above). + // If we've just added a module, descend into it. if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() { - worklist.push((mod_id, depth + 1)); + worklist.push(mod_id); } } } @@ -253,7 +212,11 @@ fn collect_trait_assoc_items( is_doc_hidden: attrs.has_doc_hidden(), is_unstable: attrs.is_unstable(), }; - map.insert(assoc_item, (smallvec![assoc_item_info], IsTraitAssocItem::Yes)); + + map.entry(assoc_item) + .or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::Yes)) + .0 + .push(assoc_item_info); } } @@ -284,7 +247,7 @@ impl fmt::Debug for ImportMap { } /// A way to match import map contents against the search query. -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] enum SearchMode { /// Import map entry should strictly match the query string. Exact, @@ -426,7 +389,7 @@ pub fn search_dependencies( while let Some((_, indexed_values)) = stream.next() { for &IndexedValue { index, value } in indexed_values { let import_map = &import_maps[index]; - let [importable, importables @ ..] = &import_map.importables[value as usize..] else { + let importables @ [importable, ..] = &import_map.importables[value as usize..] else { continue; }; @@ -441,38 +404,32 @@ pub fn search_dependencies( else { continue; }; - res.insert(*importable); - if !importables.is_empty() { - // FIXME: so many allocs... - // Name shared by the importable items in this group. - let common_importable_name = - common_importable_data.name.to_smol_str().to_ascii_lowercase(); - // Add the items from this name group. Those are all subsequent items in - // `importables` whose name match `common_importable_name`. - let iter = importables - .iter() - .copied() - .take_while(|item| { + // FIXME: so many allocs... + // Name shared by the importable items in this group. + let common_importable_name = + common_importable_data.name.to_smol_str().to_ascii_lowercase(); + // Add the items from this name group. Those are all subsequent items in + // `importables` whose name match `common_importable_name`. + let iter = importables + .iter() + .copied() + .take_while(|item| { + let &(ref import_infos, assoc_mode) = &import_map.map[item]; + query.matches_assoc_mode(assoc_mode) + && import_infos.iter().any(|info| { + info.name.to_smol_str().to_ascii_lowercase() == common_importable_name + }) + }) + .filter(|item| { + !query.case_sensitive || { + // we've already checked the common importables name case-insensitively let &(ref import_infos, assoc_mode) = &import_map.map[item]; query.matches_assoc_mode(assoc_mode) - && import_infos.iter().any(|info| { - info.name.to_smol_str().to_ascii_lowercase() - == common_importable_name - }) - }) - .filter(|item| { - !query.case_sensitive || { - // we've already checked the common importables name case-insensitively - let &(ref import_infos, assoc_mode) = &import_map.map[item]; - query.matches_assoc_mode(assoc_mode) - && import_infos - .iter() - .any(|info| query.import_matches(db, info, false)) - } - }); - res.extend(iter); - } + && import_infos.iter().any(|info| query.import_matches(db, info, false)) + } + }); + res.extend(iter); if res.len() >= query.limit { return res; @@ -665,6 +622,7 @@ mod tests { main: - publ1 (t) - real_pu2 (t) + - real_pu2::Pub (t) - real_pub (t) - real_pub::Pub (t) "#]], @@ -690,6 +648,7 @@ mod tests { - sub (t) - sub::Def (t) - sub::subsub (t) + - sub::subsub::Def (t) "#]], ); } @@ -789,7 +748,9 @@ mod tests { - module (t) - module::S (t) - module::S (v) + - module::module (t) - sub (t) + - sub::module (t) "#]], ); } From ba61766217444d980a3a20f378181e2ad388ab0e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 11 Nov 2023 14:52:11 +0100 Subject: [PATCH 3/4] Add config for preferring / ignoring prelude modules in find_path --- crates/hir-def/src/find_path.rs | 138 ++++++++++++++---- crates/hir-ty/src/display.rs | 1 + crates/hir/src/lib.rs | 11 +- crates/ide-assists/src/assist_config.rs | 1 + .../src/handlers/add_missing_match_arms.rs | 33 ++++- .../ide-assists/src/handlers/auto_import.rs | 1 + .../ide-assists/src/handlers/bool_to_enum.rs | 1 + .../src/handlers/convert_into_to_from.rs | 7 +- .../convert_tuple_return_type_to_struct.rs | 1 + .../src/handlers/extract_function.rs | 1 + .../extract_struct_from_enum_variant.rs | 1 + .../src/handlers/generate_deref.rs | 16 +- .../ide-assists/src/handlers/generate_new.rs | 1 + .../src/handlers/qualify_method_call.rs | 1 + .../ide-assists/src/handlers/qualify_path.rs | 7 +- .../replace_derive_with_manual_impl.rs | 7 +- .../replace_qualified_name_with_use.rs | 1 + crates/ide-assists/src/tests.rs | 2 + crates/ide-completion/src/completions.rs | 1 + crates/ide-completion/src/completions/expr.rs | 2 + .../src/completions/flyimport.rs | 21 ++- crates/ide-completion/src/config.rs | 1 + crates/ide-completion/src/lib.rs | 1 + crates/ide-completion/src/snippet.rs | 1 + crates/ide-completion/src/tests.rs | 1 + crates/ide-db/src/imports/import_assets.rs | 19 ++- crates/ide-db/src/path_transform.rs | 9 +- .../src/handlers/json_is_not_rust.rs | 2 + .../src/handlers/missing_fields.rs | 1 + crates/ide-diagnostics/src/lib.rs | 2 + crates/ide-ssr/src/matching.rs | 2 +- .../rust-analyzer/src/cli/analysis_stats.rs | 3 +- crates/rust-analyzer/src/config.rs | 5 + .../src/integrated_benchmarks.rs | 2 + docs/user/generated_config.adoc | 5 + editors/code/package.json | 5 + 36 files changed, 260 insertions(+), 54 deletions(-) diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs index 2a0a450097..1ebd1ba0e6 100644 --- a/crates/hir-def/src/find_path.rs +++ b/crates/hir-def/src/find_path.rs @@ -21,9 +21,10 @@ pub fn find_path( item: ItemInNs, from: ModuleId, prefer_no_std: bool, + prefer_prelude: bool, ) -> Option { let _p = profile::span("find_path"); - find_path_inner(db, item, from, None, prefer_no_std) + find_path_inner(db, item, from, None, prefer_no_std, prefer_prelude) } pub fn find_path_prefixed( @@ -32,9 +33,10 @@ pub fn find_path_prefixed( from: ModuleId, prefix_kind: PrefixKind, prefer_no_std: bool, + prefer_prelude: bool, ) -> Option { let _p = profile::span("find_path_prefixed"); - find_path_inner(db, item, from, Some(prefix_kind), prefer_no_std) + find_path_inner(db, item, from, Some(prefix_kind), prefer_no_std, prefer_prelude) } #[derive(Copy, Clone, Debug)] @@ -88,6 +90,7 @@ fn find_path_inner( from: ModuleId, prefixed: Option, prefer_no_std: bool, + prefer_prelude: bool, ) -> Option { // - if the item is a builtin, it's in scope if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item { @@ -109,6 +112,7 @@ fn find_path_inner( MAX_PATH_LEN, prefixed, prefer_no_std || db.crate_supports_no_std(crate_root.krate), + prefer_prelude, ) .map(|(item, _)| item); } @@ -134,6 +138,7 @@ fn find_path_inner( from, prefixed, prefer_no_std, + prefer_prelude, ) { let data = db.enum_data(variant.parent); path.push_segment(data.variants[variant.local_id].name.clone()); @@ -156,6 +161,7 @@ fn find_path_inner( from, prefixed, prefer_no_std || db.crate_supports_no_std(crate_root.krate), + prefer_prelude, scope_name, ) .map(|(item, _)| item) @@ -171,6 +177,7 @@ fn find_path_for_module( max_len: usize, prefixed: Option, prefer_no_std: bool, + prefer_prelude: bool, ) -> Option<(ModPath, Stability)> { if max_len == 0 { return None; @@ -236,6 +243,7 @@ fn find_path_for_module( from, prefixed, prefer_no_std, + prefer_prelude, scope_name, ) } @@ -316,6 +324,7 @@ fn calculate_best_path( from: ModuleId, mut prefixed: Option, prefer_no_std: bool, + prefer_prelude: bool, scope_name: Option, ) -> Option<(ModPath, Stability)> { if max_len <= 1 { @@ -351,11 +360,14 @@ fn calculate_best_path( best_path_len - 1, prefixed, prefer_no_std, + prefer_prelude, ) { path.0.push_segment(name); let new_path = match best_path.take() { - Some(best_path) => select_best_path(best_path, path, prefer_no_std), + Some(best_path) => { + select_best_path(best_path, path, prefer_no_std, prefer_prelude) + } None => path, }; best_path_len = new_path.0.len(); @@ -388,6 +400,7 @@ fn calculate_best_path( max_len - 1, prefixed, prefer_no_std, + prefer_prelude, ) else { continue; }; @@ -400,7 +413,9 @@ fn calculate_best_path( ); let new_path_with_stab = match best_path.take() { - Some(best_path) => select_best_path(best_path, path_with_stab, prefer_no_std), + Some(best_path) => { + select_best_path(best_path, path_with_stab, prefer_no_std, prefer_prelude) + } None => path_with_stab, }; update_best_path(&mut best_path, new_path_with_stab); @@ -421,17 +436,39 @@ fn calculate_best_path( } } +/// Select the best (most relevant) path between two paths. +/// This accounts for stability, path length whether std should be chosen over alloc/core paths as +/// well as ignoring prelude like paths or not. fn select_best_path( - old_path: (ModPath, Stability), - new_path: (ModPath, Stability), + old_path @ (_, old_stability): (ModPath, Stability), + new_path @ (_, new_stability): (ModPath, Stability), prefer_no_std: bool, + prefer_prelude: bool, ) -> (ModPath, Stability) { - match (old_path.1, new_path.1) { + match (old_stability, new_stability) { (Stable, Unstable) => return old_path, (Unstable, Stable) => return new_path, _ => {} } const STD_CRATES: [Name; 3] = [known::std, known::core, known::alloc]; + + let choose = |new_path: (ModPath, _), old_path: (ModPath, _)| { + let new_has_prelude = new_path.0.segments().iter().any(|seg| seg == &known::prelude); + let old_has_prelude = old_path.0.segments().iter().any(|seg| seg == &known::prelude); + match (new_has_prelude, old_has_prelude, prefer_prelude) { + (true, false, true) | (false, true, false) => new_path, + (true, false, false) | (false, true, true) => old_path, + // no prelude difference in the paths, so pick the smaller one + (true, true, _) | (false, false, _) => { + if new_path.0.len() < old_path.0.len() { + new_path + } else { + old_path + } + } + } + }; + match (old_path.0.segments().first(), new_path.0.segments().first()) { (Some(old), Some(new)) if STD_CRATES.contains(old) && STD_CRATES.contains(new) => { let rank = match prefer_no_std { @@ -452,23 +489,11 @@ fn select_best_path( let orank = rank(old); match nrank.cmp(&orank) { Ordering::Less => old_path, - Ordering::Equal => { - if new_path.0.len() < old_path.0.len() { - new_path - } else { - old_path - } - } + Ordering::Equal => choose(new_path, old_path), Ordering::Greater => new_path, } } - _ => { - if new_path.0.len() < old_path.0.len() { - new_path - } else { - old_path - } - } + _ => choose(new_path, old_path), } } @@ -571,7 +596,13 @@ mod tests { /// `code` needs to contain a cursor marker; checks that `find_path` for the /// item the `path` refers to returns that same path when called from the /// module the cursor is in. - fn check_found_path_(ra_fixture: &str, path: &str, prefix_kind: Option) { + #[track_caller] + fn check_found_path_( + ra_fixture: &str, + path: &str, + prefix_kind: Option, + prefer_prelude: bool, + ) { let (db, pos) = TestDB::with_position(ra_fixture); let module = db.module_at_position(pos); let parsed_path_file = syntax::SourceFile::parse(&format!("use {path};")); @@ -590,10 +621,16 @@ mod tests { ) .0 .take_types() - .unwrap(); + .expect("path does not resolve to a type"); - let found_path = - find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, false); + let found_path = find_path_inner( + &db, + ItemInNs::Types(resolved), + module, + prefix_kind, + false, + prefer_prelude, + ); assert_eq!(found_path, Some(mod_path), "on kind: {prefix_kind:?}"); } @@ -604,10 +641,23 @@ mod tests { absolute: &str, self_prefixed: &str, ) { - check_found_path_(ra_fixture, unprefixed, None); - check_found_path_(ra_fixture, prefixed, Some(PrefixKind::Plain)); - check_found_path_(ra_fixture, absolute, Some(PrefixKind::ByCrate)); - check_found_path_(ra_fixture, self_prefixed, Some(PrefixKind::BySelf)); + check_found_path_(ra_fixture, unprefixed, None, false); + check_found_path_(ra_fixture, prefixed, Some(PrefixKind::Plain), false); + check_found_path_(ra_fixture, absolute, Some(PrefixKind::ByCrate), false); + check_found_path_(ra_fixture, self_prefixed, Some(PrefixKind::BySelf), false); + } + + fn check_found_path_prelude( + ra_fixture: &str, + unprefixed: &str, + prefixed: &str, + absolute: &str, + self_prefixed: &str, + ) { + check_found_path_(ra_fixture, unprefixed, None, true); + check_found_path_(ra_fixture, prefixed, Some(PrefixKind::Plain), true); + check_found_path_(ra_fixture, absolute, Some(PrefixKind::ByCrate), true); + check_found_path_(ra_fixture, self_prefixed, Some(PrefixKind::BySelf), true); } #[test] @@ -1422,4 +1472,34 @@ pub mod error { "std::error::Error", ); } + + #[test] + fn respects_prelude_setting() { + let ra_fixture = r#" +//- /main.rs crate:main deps:krate +$0 +//- /krate.rs crate:krate +pub mod prelude { + pub use crate::foo::*; +} + +pub mod foo { + pub struct Foo; +} +"#; + check_found_path( + ra_fixture, + "krate::foo::Foo", + "krate::foo::Foo", + "krate::foo::Foo", + "krate::foo::Foo", + ); + check_found_path_prelude( + ra_fixture, + "krate::prelude::Foo", + "krate::prelude::Foo", + "krate::prelude::Foo", + "krate::prelude::Foo", + ); + } } diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index f6d6b00d74..9ccf467358 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -945,6 +945,7 @@ impl HirDisplay for Ty { ItemInNs::Types((*def_id).into()), module_id, false, + true, ) { write!(f, "{}", path.display(f.db.upcast()))?; } else { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 17ffb9acbd..6fcc02fb9d 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -664,8 +664,15 @@ impl Module { db: &dyn DefDatabase, item: impl Into, prefer_no_std: bool, + prefer_prelude: bool, ) -> Option { - hir_def::find_path::find_path(db, item.into().into(), self.into(), prefer_no_std) + hir_def::find_path::find_path( + db, + item.into().into(), + self.into(), + prefer_no_std, + prefer_prelude, + ) } /// Finds a path that can be used to refer to the given item from within @@ -676,6 +683,7 @@ impl Module { item: impl Into, prefix_kind: PrefixKind, prefer_no_std: bool, + prefer_prelude: bool, ) -> Option { hir_def::find_path::find_path_prefixed( db, @@ -683,6 +691,7 @@ impl Module { self.into(), prefix_kind, prefer_no_std, + prefer_prelude, ) } } diff --git a/crates/ide-assists/src/assist_config.rs b/crates/ide-assists/src/assist_config.rs index b273ebc85a..fbe17dbfd7 100644 --- a/crates/ide-assists/src/assist_config.rs +++ b/crates/ide-assists/src/assist_config.rs @@ -14,5 +14,6 @@ pub struct AssistConfig { pub allowed: Option>, pub insert_use: InsertUseConfig, pub prefer_no_std: bool, + pub prefer_prelude: bool, pub assist_emit_must_use: bool, } diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs index c8b78b0941..2374da9a34 100644 --- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -88,7 +88,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .into_iter() .filter_map(|variant| { Some(( - build_pat(ctx.db(), module, variant, ctx.config.prefer_no_std)?, + build_pat( + ctx.db(), + module, + variant, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + )?, variant.should_be_hidden(ctx.db(), module.krate()), )) }) @@ -140,7 +146,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .iter() .any(|variant| variant.should_be_hidden(ctx.db(), module.krate())); let patterns = variants.into_iter().filter_map(|variant| { - build_pat(ctx.db(), module, variant, ctx.config.prefer_no_std) + build_pat( + ctx.db(), + module, + variant, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) }); (ast::Pat::from(make::tuple_pat(patterns)), is_hidden) @@ -173,7 +185,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .iter() .any(|variant| variant.should_be_hidden(ctx.db(), module.krate())); let patterns = variants.into_iter().filter_map(|variant| { - build_pat(ctx.db(), module, variant.clone(), ctx.config.prefer_no_std) + build_pat( + ctx.db(), + module, + variant.clone(), + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) }); (ast::Pat::from(make::slice_pat(patterns)), is_hidden) }) @@ -440,11 +458,16 @@ fn build_pat( module: hir::Module, var: ExtendedVariant, prefer_no_std: bool, + prefer_prelude: bool, ) -> Option { match var { ExtendedVariant::Variant(var) => { - let path = - mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var), prefer_no_std)?); + let path = mod_path_to_ast(&module.find_use_path( + db, + ModuleDef::from(var), + prefer_no_std, + prefer_prelude, + )?); // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though Some(match var.source(db)?.value.kind() { diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs index cafd57a977..f508c42c53 100644 --- a/crates/ide-assists/src/handlers/auto_import.rs +++ b/crates/ide-assists/src/handlers/auto_import.rs @@ -93,6 +93,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< &ctx.sema, ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std, + ctx.config.prefer_no_std, ); if proposed_imports.is_empty() { return None; diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 082839118c..11facc5bee 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -348,6 +348,7 @@ fn augment_references_with_imports( ModuleDef::Module(*target_module), ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std, + ctx.config.prefer_prelude, ) .map(|mod_path| { make::path_concat(mod_path_to_ast(&mod_path), make::path_from_text("Bool")) diff --git a/crates/ide-assists/src/handlers/convert_into_to_from.rs b/crates/ide-assists/src/handlers/convert_into_to_from.rs index 872b52c98f..d649f13d6e 100644 --- a/crates/ide-assists/src/handlers/convert_into_to_from.rs +++ b/crates/ide-assists/src/handlers/convert_into_to_from.rs @@ -50,7 +50,12 @@ pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_>) - _ => return None, }; - mod_path_to_ast(&module.find_use_path(ctx.db(), src_type_def, ctx.config.prefer_no_std)?) + mod_path_to_ast(&module.find_use_path( + ctx.db(), + src_type_def, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + )?) }; let dest_type = match &ast_trait { diff --git a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs index 32db5ee8da..1f3caa7db3 100644 --- a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs +++ b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs @@ -205,6 +205,7 @@ fn augment_references_with_imports( ModuleDef::Module(*target_module), ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std, + ctx.config.prefer_prelude, ) .map(|mod_path| { make::path_concat( diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index de591cfde9..6b48d15881 100644 --- a/crates/ide-assists/src/handlers/extract_function.rs +++ b/crates/ide-assists/src/handlers/extract_function.rs @@ -163,6 +163,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op ModuleDef::from(control_flow_enum), ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std, + ctx.config.prefer_prelude, ); if let Some(mod_path) = mod_path { diff --git a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index e4f64ccc75..37db27a8fc 100644 --- a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -384,6 +384,7 @@ fn process_references( *enum_module_def, ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std, + ctx.config.prefer_prelude, ); if let Some(mut mod_path) = mod_path { mod_path.pop_segment(); diff --git a/crates/ide-assists/src/handlers/generate_deref.rs b/crates/ide-assists/src/handlers/generate_deref.rs index 8154539617..473c699b59 100644 --- a/crates/ide-assists/src/handlers/generate_deref.rs +++ b/crates/ide-assists/src/handlers/generate_deref.rs @@ -58,8 +58,12 @@ fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( let module = ctx.sema.to_def(&strukt)?.module(ctx.db()); let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate())?; - let trait_path = - module.find_use_path(ctx.db(), ModuleDef::Trait(trait_), ctx.config.prefer_no_std)?; + let trait_path = module.find_use_path( + ctx.db(), + ModuleDef::Trait(trait_), + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + )?; let field_type = field.ty()?; let field_name = field.name()?; @@ -99,8 +103,12 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<() let module = ctx.sema.to_def(&strukt)?.module(ctx.db()); let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate())?; - let trait_path = - module.find_use_path(ctx.db(), ModuleDef::Trait(trait_), ctx.config.prefer_no_std)?; + let trait_path = module.find_use_path( + ctx.db(), + ModuleDef::Trait(trait_), + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + )?; let field_type = field.ty()?; let target = field.syntax().text_range(); diff --git a/crates/ide-assists/src/handlers/generate_new.rs b/crates/ide-assists/src/handlers/generate_new.rs index 824255e4f8..7bfd599660 100644 --- a/crates/ide-assists/src/handlers/generate_new.rs +++ b/crates/ide-assists/src/handlers/generate_new.rs @@ -67,6 +67,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?, ctx.config.prefer_no_std, + ctx.config.prefer_prelude, )?; let expr = use_trivial_constructor( diff --git a/crates/ide-assists/src/handlers/qualify_method_call.rs b/crates/ide-assists/src/handlers/qualify_method_call.rs index 4bf974a565..ff65aac82e 100644 --- a/crates/ide-assists/src/handlers/qualify_method_call.rs +++ b/crates/ide-assists/src/handlers/qualify_method_call.rs @@ -48,6 +48,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?, ctx.config.prefer_no_std, + ctx.config.prefer_prelude, )?; let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call, resolved_call); diff --git a/crates/ide-assists/src/handlers/qualify_path.rs b/crates/ide-assists/src/handlers/qualify_path.rs index 239149dc41..fde46db305 100644 --- a/crates/ide-assists/src/handlers/qualify_path.rs +++ b/crates/ide-assists/src/handlers/qualify_path.rs @@ -37,8 +37,11 @@ use crate::{ // ``` pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let (import_assets, syntax_under_caret) = find_importable_node(ctx)?; - let mut proposed_imports = - import_assets.search_for_relative_paths(&ctx.sema, ctx.config.prefer_no_std); + let mut proposed_imports = import_assets.search_for_relative_paths( + &ctx.sema, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ); if proposed_imports.is_empty() { return None; } 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 ac45581b7b..69a4e748b7 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 @@ -82,7 +82,12 @@ pub(crate) fn replace_derive_with_manual_impl( }) .flat_map(|trait_| { current_module - .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_), ctx.config.prefer_no_std) + .find_use_path( + ctx.sema.db, + hir::ModuleDef::Trait(trait_), + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) .as_ref() .map(mod_path_to_ast) .zip(Some(trait_)) diff --git a/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs index dbbc56958f..f03eb6118a 100644 --- a/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs @@ -68,6 +68,7 @@ pub(crate) fn replace_qualified_name_with_use( module, ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std, + ctx.config.prefer_prelude, ) }) .flatten(); diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index cc3e251a8f..566384615b 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -30,6 +30,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { skip_glob_imports: true, }, prefer_no_std: false, + prefer_prelude: true, assist_emit_must_use: false, }; @@ -44,6 +45,7 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { skip_glob_imports: true, }, prefer_no_std: false, + prefer_prelude: true, assist_emit_must_use: false, }; diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index f60ac15016..7d38c638a8 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -626,6 +626,7 @@ fn enum_variants_with_paths( ctx.db, hir::ModuleDef::from(variant), ctx.config.prefer_no_std, + ctx.config.prefer_prelude, ) { // Variants with trivial paths are already added by the existing completion logic, // so we should avoid adding these twice diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 9daa6984c3..d3c817d4b4 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -175,6 +175,7 @@ pub(crate) fn complete_expr_path( ctx.db, hir::ModuleDef::from(strukt), ctx.config.prefer_no_std, + ctx.config.prefer_prelude, ) .filter(|it| it.len() > 1); @@ -197,6 +198,7 @@ pub(crate) fn complete_expr_path( ctx.db, hir::ModuleDef::from(un), ctx.config.prefer_no_std, + ctx.config.prefer_prelude, ) .filter(|it| it.len() > 1); diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 0961021e48..d74d3b264a 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -257,7 +257,12 @@ fn import_on_the_fly( let user_input_lowercased = potential_import_name.to_lowercase(); import_assets - .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std) + .search_for_imports( + &ctx.sema, + ctx.config.insert_use.prefix_kind, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) .into_iter() .filter(ns_filter) .filter(|import| { @@ -299,7 +304,12 @@ fn import_on_the_fly_pat_( let user_input_lowercased = potential_import_name.to_lowercase(); import_assets - .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std) + .search_for_imports( + &ctx.sema, + ctx.config.insert_use.prefix_kind, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) .into_iter() .filter(ns_filter) .filter(|import| { @@ -336,7 +346,12 @@ fn import_on_the_fly_method( let user_input_lowercased = potential_import_name.to_lowercase(); import_assets - .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind, ctx.config.prefer_no_std) + .search_for_imports( + &ctx.sema, + ctx.config.insert_use.prefix_kind, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ) .into_iter() .filter(|import| { !ctx.is_item_hidden(&import.item_to_import) diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs index 3d025f284b..ed5ddde8fb 100644 --- a/crates/ide-completion/src/config.rs +++ b/crates/ide-completion/src/config.rs @@ -19,6 +19,7 @@ pub struct CompletionConfig { pub snippet_cap: Option, pub insert_use: InsertUseConfig, pub prefer_no_std: bool, + pub prefer_prelude: bool, pub snippets: Vec, pub limit: Option, } diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 2fad293d16..aaf7cd7843 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -263,6 +263,7 @@ pub fn resolve_completion_edits( candidate, config.insert_use.prefix_kind, config.prefer_no_std, + config.prefer_prelude, ) }) .find(|mod_path| mod_path.display(db).to_string() == full_import_path); diff --git a/crates/ide-completion/src/snippet.rs b/crates/ide-completion/src/snippet.rs index 343719c536..50618296ee 100644 --- a/crates/ide-completion/src/snippet.rs +++ b/crates/ide-completion/src/snippet.rs @@ -179,6 +179,7 @@ fn import_edits(ctx: &CompletionContext<'_>, requires: &[GreenNode]) -> Option 1).then(|| LocatedImport::new(path.clone(), item, item, None))) }; diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 284bdd8af2..9db8e972dd 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -68,6 +68,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), prefer_no_std: false, + prefer_prelude: true, insert_use: InsertUseConfig { granularity: ImportGranularity::Crate, prefix_kind: PrefixKind::Plain, diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs index da5a951f0b..04263d15d0 100644 --- a/crates/ide-db/src/imports/import_assets.rs +++ b/crates/ide-db/src/imports/import_assets.rs @@ -220,9 +220,10 @@ impl ImportAssets { sema: &Semantics<'_, RootDatabase>, prefix_kind: PrefixKind, prefer_no_std: bool, + prefer_prelude: bool, ) -> Vec { let _p = profile::span("import_assets::search_for_imports"); - self.search_for(sema, Some(prefix_kind), prefer_no_std) + self.search_for(sema, Some(prefix_kind), prefer_no_std, prefer_prelude) } /// This may return non-absolute paths if a part of the returned path is already imported into scope. @@ -230,9 +231,10 @@ impl ImportAssets { &self, sema: &Semantics<'_, RootDatabase>, prefer_no_std: bool, + prefer_prelude: bool, ) -> Vec { let _p = profile::span("import_assets::search_for_relative_paths"); - self.search_for(sema, None, prefer_no_std) + self.search_for(sema, None, prefer_no_std, prefer_prelude) } /// Requires imports to by prefix instead of fuzzily. @@ -270,6 +272,7 @@ impl ImportAssets { sema: &Semantics<'_, RootDatabase>, prefixed: Option, prefer_no_std: bool, + prefer_prelude: bool, ) -> Vec { let _p = profile::span("import_assets::search_for"); @@ -281,6 +284,7 @@ impl ImportAssets { &self.module_with_candidate, prefixed, prefer_no_std, + prefer_prelude, ) }; @@ -594,11 +598,18 @@ fn get_mod_path( module_with_candidate: &Module, prefixed: Option, prefer_no_std: bool, + prefer_prelude: bool, ) -> Option { if let Some(prefix_kind) = prefixed { - module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind, prefer_no_std) + module_with_candidate.find_use_path_prefixed( + db, + item_to_search, + prefix_kind, + prefer_no_std, + prefer_prelude, + ) } else { - module_with_candidate.find_use_path(db, item_to_search, prefer_no_std) + module_with_candidate.find_use_path(db, item_to_search, prefer_no_std, prefer_prelude) } } diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs index fb75b5b458..fa9339f30f 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -277,6 +277,7 @@ impl Ctx<'_> { self.source_scope.db.upcast(), hir::ModuleDef::Trait(trait_ref), false, + true, )?; match make::ty_path(mod_path_to_ast(&found_path)) { ast::Type::PathType(path_ty) => Some(path_ty), @@ -311,8 +312,12 @@ impl Ctx<'_> { } } - let found_path = - self.target_module.find_use_path(self.source_scope.db.upcast(), def, false)?; + let found_path = self.target_module.find_use_path( + self.source_scope.db.upcast(), + def, + false, + true, + )?; let res = mod_path_to_ast(&found_path).clone_for_update(); if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) { if let Some(segment) = res.segment() { diff --git a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs index a337e2660d..659b74445f 100644 --- a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs +++ b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs @@ -136,6 +136,7 @@ pub(crate) fn json_in_items( it, config.insert_use.prefix_kind, config.prefer_no_std, + config.prefer_prelude, ) { insert_use(&scope, mod_path_to_ast(&it), &config.insert_use); } @@ -148,6 +149,7 @@ pub(crate) fn json_in_items( it, config.insert_use.prefix_kind, config.prefer_no_std, + config.prefer_prelude, ) { insert_use(&scope, mod_path_to_ast(&it), &config.insert_use); } diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs index c09be3fee7..d7dca1083a 100644 --- a/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -122,6 +122,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option Date: Sat, 11 Nov 2023 15:42:09 +0100 Subject: [PATCH 4/4] Fix some FIXMEs --- crates/hir-def/src/import_map.rs | 99 ++++++++++++++++++-------------- crates/hir-def/src/lib.rs | 2 +- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index b857392f37..649ea13888 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -22,7 +22,7 @@ use crate::{ type FxIndexMap = IndexMap>; /// Item import details stored in the `ImportMap`. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ImportInfo { /// A name that can be used to import the item, relative to the crate's root. pub name: Name, @@ -78,7 +78,8 @@ impl ImportMap { // Build the FST, taking care not to insert duplicate values. let mut builder = fst::MapBuilder::memory(); - let iter = importables.iter().enumerate().dedup_by(|lhs, rhs| lhs.1 .1 == rhs.1 .1); + let iter = + importables.iter().enumerate().dedup_by(|(_, (_, lhs)), (_, (_, rhs))| lhs == rhs); for (start_idx, (_, name)) in iter { let _ = builder.insert(name, start_idx as u64); } @@ -128,7 +129,6 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex { } }); - // FIXME: This loop might add the same entry up to 3 times per item! dedup for (name, per_ns) in visible_items { for (item, import) in per_ns.iter_items() { let attr_id = if let Some(import) = import { @@ -164,10 +164,10 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex { ); } - map.entry(item) - .or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::No)) - .0 - .push(import_info); + let (infos, _) = + map.entry(item).or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::No)); + infos.reserve_exact(1); + infos.push(import_info); // If we've just added a module, descend into it. if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() { @@ -213,10 +213,10 @@ fn collect_trait_assoc_items( is_unstable: attrs.is_unstable(), }; - map.entry(assoc_item) - .or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::Yes)) - .0 - .push(assoc_item_info); + let (infos, _) = + map.entry(assoc_item).or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::Yes)); + infos.reserve_exact(1); + infos.push(assoc_item_info); } } @@ -234,10 +234,13 @@ impl fmt::Debug for ImportMap { let mut importable_names: Vec<_> = self .map .iter() - .map(|(item, _)| match item { - ItemInNs::Types(it) => format!("- {it:?} (t)",), - ItemInNs::Values(it) => format!("- {it:?} (v)",), - ItemInNs::Macros(it) => format!("- {it:?} (m)",), + .map(|(item, (infos, _))| { + let l = infos.len(); + match item { + ItemInNs::Types(it) => format!("- {it:?} (t) [{l}]",), + ItemInNs::Values(it) => format!("- {it:?} (v) [{l}]",), + ItemInNs::Macros(it) => format!("- {it:?} (m) [{l}]",), + } }) .collect(); @@ -368,7 +371,7 @@ impl Query { pub fn search_dependencies( db: &dyn DefDatabase, krate: CrateId, - query: Query, + ref query: Query, ) -> FxHashSet { let _p = profile::span("search_dependencies").detail(|| format!("{query:?}")); @@ -386,6 +389,7 @@ pub fn search_dependencies( let mut stream = op.union(); let mut res = FxHashSet::default(); + let mut common_importable_data_scratch = vec![]; while let Some((_, indexed_values)) = stream.next() { for &IndexedValue { index, value } in indexed_values { let import_map = &import_maps[index]; @@ -398,36 +402,45 @@ pub fn search_dependencies( continue; } - // FIXME: We probably need to account for other possible matches in this alias group? - let Some(common_importable_data) = - importable_data.iter().find(|&info| query.import_matches(db, info, true)) - else { + common_importable_data_scratch.extend( + importable_data + .iter() + .filter(|&info| query.import_matches(db, info, true)) + // Name shared by the importable items in this group. + .map(|info| info.name.to_smol_str()), + ); + if common_importable_data_scratch.is_empty() { continue; - }; + } + common_importable_data_scratch.sort(); + common_importable_data_scratch.dedup(); - // FIXME: so many allocs... - // Name shared by the importable items in this group. - let common_importable_name = - common_importable_data.name.to_smol_str().to_ascii_lowercase(); - // Add the items from this name group. Those are all subsequent items in - // `importables` whose name match `common_importable_name`. - let iter = importables - .iter() - .copied() - .take_while(|item| { - let &(ref import_infos, assoc_mode) = &import_map.map[item]; - query.matches_assoc_mode(assoc_mode) - && import_infos.iter().any(|info| { - info.name.to_smol_str().to_ascii_lowercase() == common_importable_name + let iter = + common_importable_data_scratch.drain(..).flat_map(|common_importable_name| { + // Add the items from this name group. Those are all subsequent items in + // `importables` whose name match `common_importable_name`. + importables + .iter() + .copied() + .take_while(move |item| { + let &(ref import_infos, assoc_mode) = &import_map.map[item]; + query.matches_assoc_mode(assoc_mode) + && import_infos.iter().any(|info| { + info.name + .to_smol_str() + .eq_ignore_ascii_case(&common_importable_name) + }) + }) + .filter(move |item| { + !query.case_sensitive || { + // we've already checked the common importables name case-insensitively + let &(ref import_infos, assoc_mode) = &import_map.map[item]; + query.matches_assoc_mode(assoc_mode) + && import_infos + .iter() + .any(|info| query.import_matches(db, info, false)) + } }) - }) - .filter(|item| { - !query.case_sensitive || { - // we've already checked the common importables name case-insensitively - let &(ref import_infos, assoc_mode) = &import_map.map[item]; - query.matches_assoc_mode(assoc_mode) - && import_infos.iter().any(|info| query.import_matches(db, info, false)) - } }); res.extend(iter); diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index 495e2d4769..fd8f64d670 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -152,7 +152,7 @@ impl TryFrom for CrateRootModuleId { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ModuleId { krate: CrateId, /// If this `ModuleId` was derived from a `DefMap` for a block expression, this stores the