diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs index f46054e8cc5e..d2c33d923cbb 100644 --- a/crates/hir-def/src/find_path.rs +++ b/crates/hir-def/src/find_path.rs @@ -1,6 +1,6 @@ //! An algorithm to find a path to refer to a certain item. -use std::iter; +use std::{cmp::Ordering, iter}; use hir_expand::name::{known, AsName, Name}; use rustc_hash::FxHashSet; @@ -39,40 +39,6 @@ pub fn find_path_prefixed( const MAX_PATH_LEN: usize = 15; -trait ModPathExt { - fn starts_with_std(&self) -> bool; - fn can_start_with_std(&self) -> bool; -} - -impl ModPathExt for ModPath { - fn starts_with_std(&self) -> bool { - self.segments().first() == Some(&known::std) - } - - // Can we replace the first segment with `std::` and still get a valid, identical path? - fn can_start_with_std(&self) -> bool { - let first_segment = self.segments().first(); - first_segment == Some(&known::alloc) || first_segment == Some(&known::core) - } -} - -fn check_self_super(def_map: &DefMap, item: ItemInNs, from: ModuleId) -> Option { - if item == ItemInNs::Types(from.into()) { - // - if the item is the module we're in, use `self` - Some(ModPath::from_segments(PathKind::Super(0), None)) - } else if let Some(parent_id) = def_map[from.local_id].parent { - // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly) - let parent_id = def_map.module_id(parent_id); - if item == ItemInNs::Types(ModuleDefId::ModuleId(parent_id)) { - Some(ModPath::from_segments(PathKind::Super(1), None)) - } else { - None - } - } else { - None - } -} - #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PrefixKind { /// Causes paths to always start with either `self`, `super`, `crate` or a crate-name. @@ -100,6 +66,7 @@ impl PrefixKind { self == &PrefixKind::ByCrate } } + /// Attempts to find a path to refer to the given `item` visible from the `from` ModuleId fn find_path_inner( db: &dyn DefDatabase, @@ -108,146 +75,239 @@ fn find_path_inner( prefixed: Option, prefer_core: bool, ) -> Option { - // FIXME: Do fast path for std/core libs? + // - if the item is a builtin, it's in scope + if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item { + return Some(ModPath::from_segments(PathKind::Plain, Some(builtin.as_name()))); + } - let mut visited_modules = FxHashSet::default(); let def_map = from.def_map(db); - find_path_inner_( + let crate_root = def_map.crate_root(db); + // - if the item is a module, jump straight to module search + if let ItemInNs::Types(ModuleDefId::ModuleId(module_id)) = item { + let mut visited_modules = FxHashSet::default(); + return find_path_for_module( + db, + &def_map, + &mut visited_modules, + crate_root, + from, + module_id, + MAX_PATH_LEN, + prefixed, + StdPreference::new(db, crate_root, prefer_core), + ); + } + + // - if the item is already in scope, return the name under which it is + let scope_name = find_in_scope(db, &def_map, from, item); + if prefixed.is_none() { + if let Some(scope_name) = scope_name { + return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name))); + } + } + + // - if the item is in the prelude, return the name from there + if let Some(value) = find_in_prelude(db, &crate_root.def_map(db), item, from) { + return value; + } + + if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() { + // - if the item is an enum variant, refer to it via the enum + if let Some(mut path) = + find_path_inner(db, ItemInNs::Types(variant.parent.into()), from, prefixed, prefer_core) + { + let data = db.enum_data(variant.parent); + path.push_segment(data.variants[variant.local_id].name.clone()); + return Some(path); + } + // If this doesn't work, it seems we have no way of referring to the + // enum; that's very weird, but there might still be a reexport of the + // variant somewhere + } + + let mut visited_modules = FxHashSet::default(); + + calculate_best_path( db, &def_map, - from, - item, + &mut visited_modules, + crate_root, MAX_PATH_LEN, + item, + from, prefixed, - &mut visited_modules, - prefer_core, + StdPreference::new(db, crate_root, prefer_core), + scope_name, ) } -fn find_path_inner_( +fn find_path_for_module( db: &dyn DefDatabase, def_map: &DefMap, + visited_modules: &mut FxHashSet, + crate_root: ModuleId, from: ModuleId, - item: ItemInNs, + module_id: ModuleId, max_len: usize, - mut prefixed: Option, - visited_modules: &mut FxHashSet, - prefer_core: bool, + prefixed: Option, + prefer_core: StdPreference, ) -> Option { if max_len == 0 { return None; } // Base cases: - // - if the item is already in scope, return the name under which it is - let scope_name = def_map.with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { - def_map[local_id].scope.name_of(item).map(|(name, _)| name.clone()) - }); + let scope_name = find_in_scope(db, def_map, from, ItemInNs::Types(module_id.into())); if prefixed.is_none() { if let Some(scope_name) = scope_name { return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name))); } } - // - if the item is a builtin, it's in scope - if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item { - return Some(ModPath::from_segments(PathKind::Plain, Some(builtin.as_name()))); - } - // - if the item is the crate root, return `crate` - let crate_root = def_map.crate_root(db); - if item == ItemInNs::Types(ModuleDefId::ModuleId(crate_root)) { + if module_id == crate_root { return Some(ModPath::from_segments(PathKind::Crate, None)); } + // - if relative paths are fine, check if we are searching for a parent if prefixed.filter(PrefixKind::is_absolute).is_none() { - if let modpath @ Some(_) = check_self_super(&def_map, item, from) { + if let modpath @ Some(_) = find_self_super(&def_map, module_id, from) { return modpath; } } // - if the item is the crate root of a dependency crate, return the name from the extern prelude let root_def_map = crate_root.def_map(db); - if let ItemInNs::Types(ModuleDefId::ModuleId(item)) = item { - for (name, &def_id) in root_def_map.extern_prelude() { - if item == def_id { - let name = scope_name.unwrap_or_else(|| name.clone()); - - let name_already_occupied_in_type_ns = def_map - .with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { - def_map[local_id] - .scope - .type_(&name) - .filter(|&(id, _)| id != ModuleDefId::ModuleId(def_id)) - }) - .is_some(); - let kind = if name_already_occupied_in_type_ns { - cov_mark::hit!(ambiguous_crate_start); - PathKind::Abs - } else { - PathKind::Plain - }; - return Some(ModPath::from_segments(kind, Some(name))); - } + for (name, &def_id) in root_def_map.extern_prelude() { + if module_id == def_id { + let name = scope_name.unwrap_or_else(|| name.clone()); + + let name_already_occupied_in_type_ns = def_map + .with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { + def_map[local_id] + .scope + .type_(&name) + .filter(|&(id, _)| id != ModuleDefId::ModuleId(def_id)) + }) + .is_some(); + let kind = if name_already_occupied_in_type_ns { + cov_mark::hit!(ambiguous_crate_start); + PathKind::Abs + } else { + PathKind::Plain + }; + return Some(ModPath::from_segments(kind, Some(name))); } } - // - if the item is in the prelude, return the name from there + if let Some(value) = find_in_prelude(db, &root_def_map, ItemInNs::Types(module_id.into()), from) + { + return value; + } + calculate_best_path( + db, + def_map, + visited_modules, + crate_root, + max_len, + ItemInNs::Types(module_id.into()), + from, + prefixed, + prefer_core, + scope_name, + ) +} + +fn find_in_scope( + db: &dyn DefDatabase, + def_map: &DefMap, + from: ModuleId, + item: ItemInNs, +) -> Option { + def_map.with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { + def_map[local_id].scope.name_of(item).map(|(name, _)| name.clone()) + }) +} + +fn find_in_prelude( + db: &dyn DefDatabase, + root_def_map: &DefMap, + item: ItemInNs, + from: ModuleId, +) -> Option> { if let Some(prelude_module) = root_def_map.prelude() { // Preludes in block DefMaps are ignored, only the crate DefMap is searched let prelude_def_map = prelude_module.def_map(db); let prelude_scope = &prelude_def_map[prelude_module.local_id].scope; if let Some((name, vis)) = prelude_scope.name_of(item) { if vis.is_visible_from(db, from) { - return Some(ModPath::from_segments(PathKind::Plain, Some(name.clone()))); + return Some(Some(ModPath::from_segments(PathKind::Plain, Some(name.clone())))); } } } + None +} - // Recursive case: - // - if the item is an enum variant, refer to it via the enum - if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() { - if let Some(mut path) = - find_path(db, ItemInNs::Types(variant.parent.into()), from, prefer_core) - { - let data = db.enum_data(variant.parent); - path.push_segment(data.variants[variant.local_id].name.clone()); - return Some(path); +fn find_self_super(def_map: &DefMap, item: ModuleId, from: ModuleId) -> Option { + if item == from { + // - if the item is the module we're in, use `self` + Some(ModPath::from_segments(PathKind::Super(0), None)) + } else if let Some(parent_id) = def_map[from.local_id].parent { + // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly) + let parent_id = def_map.module_id(parent_id); + if item == parent_id { + Some(ModPath::from_segments(PathKind::Super(1), None)) + } else { + None } - // If this doesn't work, it seems we have no way of referring to the - // enum; that's very weird, but there might still be a reexport of the - // variant somewhere + } else { + None } +} - // - otherwise, look for modules containing (reexporting) it and import it from one of those - let prefer_no_std = prefer_core || db.crate_supports_no_std(crate_root.krate); +fn calculate_best_path( + db: &dyn DefDatabase, + def_map: &DefMap, + visited_modules: &mut FxHashSet, + crate_root: ModuleId, + max_len: usize, + item: ItemInNs, + from: ModuleId, + mut prefixed: Option, + prefer_core: StdPreference, + scope_name: Option, +) -> Option { + if max_len <= 1 { + return None; + } let mut best_path = None; - let mut best_path_len = max_len; - + // Recursive case: + // - otherwise, look for modules containing (reexporting) it and import it from one of those if item.krate(db) == Some(from.krate) { + let mut best_path_len = max_len; // Item was defined in the same crate that wants to import it. It cannot be found in any // dependency in this case. - // FIXME: this should have a fast path that doesn't look through the prelude again? for (module_id, name) in find_local_import_locations(db, item, from) { if !visited_modules.insert(module_id) { cov_mark::hit!(recursive_imports); continue; } - if let Some(mut path) = find_path_inner_( + if let Some(mut path) = find_path_for_module( db, def_map, + visited_modules, + crate_root, from, - ItemInNs::Types(ModuleDefId::ModuleId(module_id)), + module_id, best_path_len - 1, prefixed, - visited_modules, prefer_core, ) { path.push_segment(name); let new_path = match best_path { - Some(best_path) => select_best_path(best_path, path, prefer_no_std), + Some(best_path) => select_best_path(best_path, path, prefer_core), None => path, }; best_path_len = new_path.len(); @@ -265,14 +325,15 @@ fn find_path_inner_( import_map.import_info_for(item).and_then(|info| { // 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 = find_path_inner_( + let mut path = find_path_for_module( db, def_map, + visited_modules, from, - ItemInNs::Types(ModuleDefId::ModuleId(info.container)), - best_path_len - 1, + crate_root, + info.container, + max_len - 1, prefixed, - visited_modules, prefer_core, )?; cov_mark::hit!(partially_imported); @@ -283,22 +344,18 @@ fn find_path_inner_( for path in extern_paths { let new_path = match best_path { - Some(best_path) => select_best_path(best_path, path, prefer_no_std), + Some(best_path) => select_best_path(best_path, path, prefer_core), None => path, }; best_path = Some(new_path); } } - - // If the item is declared inside a block expression, don't use a prefix, as we don't handle - // that correctly (FIXME). - if let Some(item_module) = item.as_module_def_id().and_then(|did| did.module(db)) { - if item_module.def_map(db).block_id().is_some() && prefixed.is_some() { + if let Some(module) = item.module(db) { + if module.def_map(db).block_id().is_some() && prefixed.is_some() { cov_mark::hit!(prefixed_in_block_expression); prefixed = Some(PrefixKind::Plain); } } - match prefixed.map(PrefixKind::prefix) { Some(prefix) => best_path.or_else(|| { scope_name.map(|scope_name| ModPath::from_segments(prefix, Some(scope_name))) @@ -307,30 +364,77 @@ fn find_path_inner_( } } -fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath { - if old_path.starts_with_std() && new_path.can_start_with_std() { - if prefer_no_std { - cov_mark::hit!(prefer_no_std_paths); - new_path +#[derive(Copy, Clone)] +enum StdPreference { + // Prefers alloc, then core + NoStd, + // always pick std + Std, + // always pick core + Core, +} + +impl StdPreference { + fn new(db: &dyn DefDatabase, crate_root: ModuleId, prefer_core: bool) -> StdPreference { + if prefer_core { + StdPreference::Core + } else if db.crate_supports_no_std(crate_root.krate) { + StdPreference::NoStd } else { - cov_mark::hit!(prefer_std_paths); - old_path + StdPreference::Std } - } else if new_path.starts_with_std() && old_path.can_start_with_std() { - if prefer_no_std { - cov_mark::hit!(prefer_no_std_paths); - old_path - } else { - cov_mark::hit!(prefer_std_paths); - new_path + } +} + +fn select_best_path(old_path: ModPath, new_path: ModPath, preference: StdPreference) -> ModPath { + const STD_CRATES: [Name; 3] = [known::std, known::core, known::alloc]; + match (old_path.segments().first(), new_path.segments().first()) { + (Some(old), Some(new)) if STD_CRATES.contains(old) && STD_CRATES.contains(new) => { + let rank = match preference { + StdPreference::NoStd => |name: &Name| match name { + name if name == &known::core => 1, + name if name == &known::alloc => 2, + name if name == &known::std => 0, + _ => unreachable!(), + }, + StdPreference::Std => |name: &Name| match name { + name if name == &known::core => 0, + name if name == &known::alloc => 0, + name if name == &known::std => 1, + _ => unreachable!(), + }, + StdPreference::Core => |name: &Name| match name { + name if name == &known::core => 2, + name if name == &known::alloc => 1, + name if name == &known::std => 0, + _ => unreachable!(), + }, + }; + let nrank = rank(new); + let orank = rank(old); + match nrank.cmp(&orank) { + Ordering::Less => old_path, + Ordering::Equal => { + if new_path.len() < old_path.len() { + new_path + } else { + old_path + } + } + Ordering::Greater => new_path, + } + } + _ => { + if new_path.len() < old_path.len() { + new_path + } else { + old_path + } } - } else if new_path.len() < old_path.len() { - new_path - } else { - old_path } } +// FIXME: Remove allocations /// Finds locations in `from.krate` from which `item` can be imported by `from`. fn find_local_import_locations( db: &dyn DefDatabase, @@ -429,7 +533,12 @@ 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) { + fn check_found_path_( + ra_fixture: &str, + path: &str, + prefix_kind: Option, + prefer_core: 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)); @@ -450,7 +559,7 @@ mod tests { .unwrap(); let found_path = - find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, false); + find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, prefer_core); assert_eq!(found_path, Some(mod_path), "{:?}", prefix_kind); } @@ -461,10 +570,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_prefer_core( + 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] @@ -490,8 +612,8 @@ $0 "#, "E::A", "E::A", - "E::A", - "E::A", + "crate::E::A", + "self::E::A", ); } @@ -810,7 +932,6 @@ pub use super::foo; #[test] fn prefer_std_paths_over_alloc() { - cov_mark::check!(prefer_std_paths); check_found_path( r#" //- /main.rs crate:main deps:alloc,std @@ -835,7 +956,6 @@ pub mod sync { #[test] fn prefer_core_paths_over_std() { - cov_mark::check!(prefer_no_std_paths); check_found_path( r#" //- /main.rs crate:main deps:core,std @@ -938,6 +1058,58 @@ pub struct Arc; ); } + #[test] + fn prefer_core_setting() { + check_found_path_prefer_core( + r#" +//- /main.rs crate:main deps:core,std +$0 + +//- /std.rs crate:std deps:core + +pub mod fmt { + pub use core::fmt::Error; +} + +//- /zzz.rs crate:core + +pub mod fmt { + pub struct Error; +} + "#, + "core::fmt::Error", + "core::fmt::Error", + "core::fmt::Error", + "core::fmt::Error", + ); + } + + #[test] + fn prefer_core_setting_prefer_alloc_over_std() { + check_found_path_prefer_core( + r#" +//- /main.rs crate:main deps:alloc,std +$0 + +//- /std.rs crate:std deps:alloc + +pub mod fmt { + pub use core::fmt::Error; +} + +//- /zzz.rs crate:alloc + +pub mod fmt { + pub struct Error; +} + "#, + "alloc::fmt::Error", + "alloc::fmt::Error", + "alloc::fmt::Error", + "alloc::fmt::Error", + ); + } + #[test] fn builtins_are_in_scope() { let code = r#" diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index a11a92204c15..a40656fd6a42 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -457,8 +457,15 @@ impl ItemInNs { /// Returns the crate defining this item (or `None` if `self` is built-in). pub fn krate(&self, db: &dyn DefDatabase) -> Option { match self { - ItemInNs::Types(did) | ItemInNs::Values(did) => did.module(db).map(|m| m.krate), + ItemInNs::Types(id) | ItemInNs::Values(id) => id.module(db).map(|m| m.krate), ItemInNs::Macros(id) => Some(id.module(db).krate), } } + + pub fn module(&self, db: &dyn DefDatabase) -> Option { + match self { + ItemInNs::Types(id) | ItemInNs::Values(id) => id.module(db), + ItemInNs::Macros(id) => Some(id.module(db)), + } + } } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e0384e67d466..34de819ddbb3 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -262,7 +262,7 @@ config_data! { imports_group_enable: bool = "true", /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`. imports_merge_glob: bool = "true", - /// Prefer to use imports of the core crate over the std crate. + /// Prefer to use imports of the core crate, then alloc and finally std crate. imports_prefer_core: bool = "false", /// The path structure for newly inserted paths to use. imports_prefix: ImportPrefixDef = "\"plain\"",