1 //! An algorithm to find a path to refer to a certain item.
5 use hir_expand::name::{known, AsName, Name};
12 path::{ModPath, PathKind},
13 visibility::Visibility,
14 CrateId, ModuleDefId, ModuleId,
17 // FIXME: handle local items
19 /// Find a path that can be used to refer to a certain item. This can depend on
20 /// *from where* you're referring to the item, hence the `from` parameter.
21 pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
22 let _p = profile("find_path");
23 find_path_inner(db, item, from, MAX_PATH_LEN)
26 const MAX_PATH_LEN: usize = 15;
29 fn starts_with_std(&self) -> bool {
30 self.segments.first() == Some(&known::std)
33 // When std library is present, paths starting with `std::`
34 // should be preferred over paths starting with `core::` and `alloc::`
35 fn can_start_with_std(&self) -> bool {
36 let first_segment = self.segments.first();
37 first_segment == Some(&known::alloc) || first_segment == Some(&known::core)
40 fn len(&self) -> usize {
44 PathKind::Super(i) => i as usize,
47 PathKind::DollarCrate(_) => 1,
57 ) -> Option<ModPath> {
64 // - if the item is already in scope, return the name under which it is
65 let def_map = db.crate_def_map(from.krate);
66 let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope;
67 if let Some((name, _)) = from_scope.name_of(item) {
68 return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()]));
71 // - if the item is the crate root, return `crate`
73 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
75 local_id: def_map.root,
78 return Some(ModPath::from_segments(PathKind::Crate, Vec::new()));
81 // - if the item is the module we're in, use `self`
82 if item == ItemInNs::Types(from.into()) {
83 return Some(ModPath::from_segments(PathKind::Super(0), Vec::new()));
86 // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly)
87 if let Some(parent_id) = def_map.modules[from.local_id].parent {
89 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
94 return Some(ModPath::from_segments(PathKind::Super(1), Vec::new()));
98 // - if the item is the crate root of a dependency crate, return the name from the extern prelude
99 for (name, def_id) in &def_map.extern_prelude {
100 if item == ItemInNs::Types(*def_id) {
101 return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()]));
105 // - if the item is in the prelude, return the name from there
106 if let Some(prelude_module) = def_map.prelude {
107 let prelude_def_map = db.crate_def_map(prelude_module.krate);
108 let prelude_scope: &crate::item_scope::ItemScope =
109 &prelude_def_map.modules[prelude_module.local_id].scope;
110 if let Some((name, vis)) = prelude_scope.name_of(item) {
111 if vis.is_visible_from(db, from) {
112 return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()]));
117 // - if the item is a builtin, it's in scope
118 if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item {
119 return Some(ModPath::from_segments(PathKind::Plain, vec![builtin.as_name()]));
123 // - if the item is an enum variant, refer to it via the enum
124 if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() {
125 if let Some(mut path) = find_path(db, ItemInNs::Types(variant.parent.into()), from) {
126 let data = db.enum_data(variant.parent);
127 path.segments.push(data.variants[variant.local_id].name.clone());
130 // If this doesn't work, it seems we have no way of referring to the
131 // enum; that's very weird, but there might still be a reexport of the
135 // - otherwise, look for modules containing (reexporting) it and import it from one of those
136 let crate_root = ModuleId { local_id: def_map.root, krate: from.krate };
137 let crate_attrs = db.attrs(crate_root.into());
138 let prefer_no_std = crate_attrs.by_key("no_std").exists();
139 let importable_locations = find_importable_locations(db, item, from);
140 let mut best_path = None;
141 let mut best_path_len = max_len;
142 for (module_id, name) in importable_locations {
143 let mut path = match find_path_inner(
145 ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
152 path.segments.push(name);
154 let new_path = if let Some(best_path) = best_path {
155 select_best_path(best_path, path, prefer_no_std)
159 best_path_len = new_path.len();
160 best_path = Some(new_path);
165 fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath {
166 if old_path.starts_with_std() && new_path.can_start_with_std() {
168 mark::hit!(prefer_no_std_paths);
171 mark::hit!(prefer_std_paths);
174 } else if new_path.starts_with_std() && old_path.can_start_with_std() {
176 mark::hit!(prefer_no_std_paths);
179 mark::hit!(prefer_std_paths);
182 } else if new_path.len() < old_path.len() {
189 fn find_importable_locations(
190 db: &dyn DefDatabase,
193 ) -> Vec<(ModuleId, Name)> {
194 let crate_graph = db.crate_graph();
195 let mut result = Vec::new();
196 // We only look in the crate from which we are importing, and the direct
197 // dependencies. We cannot refer to names from transitive dependencies
198 // directly (only through reexports in direct dependencies).
199 for krate in Some(from.krate)
201 .chain(crate_graph[from.krate].dependencies.iter().map(|dep| dep.crate_id))
204 db.importable_locations_of(item, krate)
206 .filter(|(_, _, vis)| vis.is_visible_from(db, from))
207 .map(|(m, n, _)| (*m, n.clone())),
213 /// Collects all locations from which we might import the item in a particular
214 /// crate. These include the original definition of the item, and any
215 /// non-private `use`s.
217 /// Note that the crate doesn't need to be the one in which the item is defined;
218 /// it might be re-exported in other crates.
219 pub(crate) fn importable_locations_of_query(
220 db: &dyn DefDatabase,
223 ) -> Arc<[(ModuleId, Name, Visibility)]> {
224 let _p = profile("importable_locations_of_query");
225 let def_map = db.crate_def_map(krate);
226 let mut result = Vec::new();
227 for (local_id, data) in def_map.modules.iter() {
228 if let Some((name, vis)) = data.scope.name_of(item) {
229 let is_private = if let Visibility::Module(private_to) = vis {
230 private_to.local_id == local_id
234 let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
235 data.scope.declarations().any(|it| it == module_def_id)
239 if is_private && !is_original_def {
240 // Ignore private imports. these could be used if we are
241 // in a submodule of this module, but that's usually not
242 // what the user wants; and if this module can import
243 // the item and we're a submodule of it, so can we.
244 // Also this keeps the cached data smaller.
247 result.push((ModuleId { krate, local_id }, name.clone(), vis));
256 use hir_expand::hygiene::Hygiene;
257 use ra_db::fixture::WithFixture;
258 use ra_syntax::ast::AstNode;
259 use test_utils::mark;
261 use crate::test_db::TestDB;
265 /// `code` needs to contain a cursor marker; checks that `find_path` for the
266 /// item the `path` refers to returns that same path when called from the
267 /// module the cursor is in.
268 fn check_found_path(code: &str, path: &str) {
269 let (db, pos) = TestDB::with_position(code);
270 let module = db.module_for_file(pos.file_id);
271 let parsed_path_file = ra_syntax::SourceFile::parse(&format!("use {};", path));
272 let ast_path = parsed_path_file
275 .find_map(ra_syntax::ast::Path::cast)
277 let mod_path = ModPath::from_src(ast_path, &Hygiene::new_unhygienic()).unwrap();
279 let crate_def_map = db.crate_def_map(module.krate);
280 let resolved = crate_def_map
285 crate::item_scope::BuiltinShadowMode::Module,
291 let found_path = find_path(&db, ItemInNs::Types(resolved), module);
293 assert_eq!(found_path, Some(mod_path));
303 check_found_path(code, "S");
313 check_found_path(code, "E::A");
325 check_found_path(code, "foo::S");
339 check_found_path(code, "super::S");
350 check_found_path(code, "self");
361 check_found_path(code, "crate");
373 check_found_path(code, "crate::S");
377 fn different_crate() {
379 //- /main.rs crate:main deps:std
381 //- /std.rs crate:std
384 check_found_path(code, "std::S");
388 fn different_crate_renamed() {
390 //- /main.rs crate:main deps:std
391 extern crate std as std_renamed;
393 //- /std.rs crate:std
396 check_found_path(code, "std_renamed::S");
400 fn same_crate_reexport() {
404 mod foo { pub(super) struct S; }
405 pub(crate) use foo::*;
409 check_found_path(code, "bar::S");
413 fn same_crate_reexport_rename() {
417 mod foo { pub(super) struct S; }
418 pub(crate) use foo::S as U;
422 check_found_path(code, "bar::U");
426 fn different_crate_reexport() {
428 //- /main.rs crate:main deps:std
430 //- /std.rs crate:std deps:core
432 //- /core.rs crate:core
435 check_found_path(code, "std::S");
441 //- /main.rs crate:main deps:std
443 //- /std.rs crate:std
444 pub mod prelude { pub struct S; }
448 check_found_path(code, "S");
452 fn enum_variant_from_prelude() {
454 //- /main.rs crate:main deps:std
456 //- /std.rs crate:std
458 pub enum Option<T> { Some(T), None }
464 check_found_path(code, "None");
465 check_found_path(code, "Some");
477 pub mod bar { pub struct S; }
479 pub use crate::foo::bar::S;
481 check_found_path(code, "baz::S");
485 fn discount_private_imports() {
489 pub mod bar { pub struct S; }
494 // crate::S would be shorter, but using private imports seems wrong
495 check_found_path(code, "crate::bar::S");
513 check_found_path(code, "crate::foo::S");
517 fn prefer_std_paths_over_alloc() {
518 mark::check!(prefer_std_paths);
520 //- /main.rs crate:main deps:alloc,std
523 //- /std.rs crate:std deps:alloc
525 pub use alloc::sync::Arc;
528 //- /zzz.rs crate:alloc
533 check_found_path(code, "std::sync::Arc");
537 fn prefer_core_paths_over_std() {
538 mark::check!(prefer_no_std_paths);
540 //- /main.rs crate:main deps:core,std
545 //- /std.rs crate:std deps:core
548 pub use core::fmt::Error;
551 //- /zzz.rs crate:core
557 check_found_path(code, "core::fmt::Error");
561 fn prefer_alloc_paths_over_std() {
563 //- /main.rs crate:main deps:alloc,std
568 //- /std.rs crate:std deps:alloc
571 pub use alloc::sync::Arc;
574 //- /zzz.rs crate:alloc
580 check_found_path(code, "alloc::sync::Arc");
584 fn prefer_shorter_paths_if_not_alloc() {
586 //- /main.rs crate:main deps:megaalloc,std
589 //- /std.rs crate:std deps:megaalloc
591 pub use megaalloc::sync::Arc;
594 //- /zzz.rs crate:megaalloc
597 check_found_path(code, "megaalloc::Arc");
601 fn builtins_are_in_scope() {
610 check_found_path(code, "u8");
611 check_found_path(code, "u16");