1 //! An algorithm to find a path to refer to a certain item.
3 use hir_expand::name::{known, AsName, Name};
4 use rustc_hash::FxHashSet;
7 use crate::nameres::CrateDefMap;
11 path::{ModPath, PathKind},
12 visibility::Visibility,
13 ModuleDefId, ModuleId,
16 // FIXME: handle local items
18 /// Find a path that can be used to refer to a certain item. This can depend on
19 /// *from where* you're referring to the item, hence the `from` parameter.
20 pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
21 let _p = profile::span("find_path");
22 find_path_inner(db, item, from, MAX_PATH_LEN, Prefixed::Not)
25 pub fn find_path_prefixed(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
26 let _p = profile::span("find_path_absolute");
27 find_path_inner(db, item, from, MAX_PATH_LEN, Prefixed::Plain)
30 const MAX_PATH_LEN: usize = 15;
33 fn starts_with_std(&self) -> bool {
34 self.segments.first() == Some(&known::std)
37 // When std library is present, paths starting with `std::`
38 // should be preferred over paths starting with `core::` and `alloc::`
39 fn can_start_with_std(&self) -> bool {
40 let first_segment = self.segments.first();
41 first_segment == Some(&known::alloc) || first_segment == Some(&known::core)
45 fn check_crate_self_super(
46 def_map: &CrateDefMap,
49 ) -> Option<ModPath> {
50 // - if the item is the crate root, return `crate`
52 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
54 local_id: def_map.root,
57 Some(ModPath::from_segments(PathKind::Crate, Vec::new()))
58 } else if item == ItemInNs::Types(from.into()) {
59 // - if the item is the module we're in, use `self`
60 Some(ModPath::from_segments(PathKind::Super(0), Vec::new()))
62 if let Some(parent_id) = def_map.modules[from.local_id].parent {
63 // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly)
65 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
70 return Some(ModPath::from_segments(PathKind::Super(1), Vec::new()));
77 #[derive(Copy, Clone, PartialEq, Eq)]
86 fn prefix(self) -> Option<PathKind> {
88 Prefixed::Not => None,
89 Prefixed::BySelf => Some(PathKind::Super(0)),
90 Prefixed::Plain => Some(PathKind::Plain),
95 fn prefixed(self) -> bool {
101 db: &dyn DefDatabase,
106 ) -> Option<ModPath> {
113 // - if the item is already in scope, return the name under which it is
114 let def_map = db.crate_def_map(from.krate);
115 let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope;
117 if let Some((name, _)) = from_scope.name_of(item) { Some(name.clone()) } else { None };
118 if !prefixed.prefixed() && scope_name.is_some() {
120 .map(|scope_name| ModPath::from_segments(PathKind::Plain, vec![scope_name]));
123 if let modpath @ Some(_) = check_crate_self_super(&def_map, item, from) {
127 // - if the item is the crate root of a dependency crate, return the name from the extern prelude
128 for (name, def_id) in &def_map.extern_prelude {
129 if item == ItemInNs::Types(*def_id) {
130 let name = scope_name.unwrap_or_else(|| name.clone());
131 return Some(ModPath::from_segments(PathKind::Plain, vec![name]));
135 // - if the item is in the prelude, return the name from there
136 if let Some(prelude_module) = def_map.prelude {
137 let prelude_def_map = db.crate_def_map(prelude_module.krate);
138 let prelude_scope: &crate::item_scope::ItemScope =
139 &prelude_def_map.modules[prelude_module.local_id].scope;
140 if let Some((name, vis)) = prelude_scope.name_of(item) {
141 if vis.is_visible_from(db, from) {
142 return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()]));
147 // - if the item is a builtin, it's in scope
148 if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item {
149 return Some(ModPath::from_segments(PathKind::Plain, vec![builtin.as_name()]));
153 // - if the item is an enum variant, refer to it via the enum
154 if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() {
155 if let Some(mut path) = find_path(db, ItemInNs::Types(variant.parent.into()), from) {
156 let data = db.enum_data(variant.parent);
157 path.segments.push(data.variants[variant.local_id].name.clone());
160 // If this doesn't work, it seems we have no way of referring to the
161 // enum; that's very weird, but there might still be a reexport of the
165 // - otherwise, look for modules containing (reexporting) it and import it from one of those
167 let crate_root = ModuleId { local_id: def_map.root, krate: from.krate };
168 let crate_attrs = db.attrs(crate_root.into());
169 let prefer_no_std = crate_attrs.by_key("no_std").exists();
170 let mut best_path = None;
171 let mut best_path_len = max_len;
173 if item.krate(db) == Some(from.krate) {
174 // Item was defined in the same crate that wants to import it. It cannot be found in any
175 // dependency in this case.
177 let local_imports = find_local_import_locations(db, item, from);
178 for (module_id, name) in local_imports {
179 if let Some(mut path) = find_path_inner(
181 ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
186 path.segments.push(name);
188 let new_path = if let Some(best_path) = best_path {
189 select_best_path(best_path, path, prefer_no_std)
193 best_path_len = new_path.len();
194 best_path = Some(new_path);
198 // Item was defined in some upstream crate. This means that it must be exported from one,
199 // too (unless we can't name it at all). It could *also* be (re)exported by the same crate
200 // that wants to import it here, but we always prefer to use the external path here.
202 let crate_graph = db.crate_graph();
203 let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| {
204 let import_map = db.import_map(dep.crate_id);
205 import_map.import_info_for(item).and_then(|info| {
206 // Determine best path for containing module and append last segment from `info`.
207 let mut path = find_path_inner(
209 ItemInNs::Types(ModuleDefId::ModuleId(info.container)),
214 path.segments.push(info.path.segments.last().unwrap().clone());
219 for path in extern_paths {
220 let new_path = if let Some(best_path) = best_path {
221 select_best_path(best_path, path, prefer_no_std)
225 best_path = Some(new_path);
229 if let Some(prefix) = prefixed.prefix() {
230 best_path.or_else(|| {
231 scope_name.map(|scope_name| ModPath::from_segments(prefix, vec![scope_name]))
238 fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath {
239 if old_path.starts_with_std() && new_path.can_start_with_std() {
241 mark::hit!(prefer_no_std_paths);
244 mark::hit!(prefer_std_paths);
247 } else if new_path.starts_with_std() && old_path.can_start_with_std() {
249 mark::hit!(prefer_no_std_paths);
252 mark::hit!(prefer_std_paths);
255 } else if new_path.len() < old_path.len() {
262 /// Finds locations in `from.krate` from which `item` can be imported by `from`.
263 fn find_local_import_locations(
264 db: &dyn DefDatabase,
267 ) -> Vec<(ModuleId, Name)> {
268 let _p = profile::span("find_local_import_locations");
270 // `from` can import anything below `from` with visibility of at least `from`, and anything
271 // above `from` with any visibility. That means we do not need to descend into private siblings
272 // of `from` (and similar).
274 let def_map = db.crate_def_map(from.krate);
276 // Compute the initial worklist. We start with all direct child modules of `from` as well as all
277 // of its (recursive) parent modules.
278 let data = &def_map.modules[from.local_id];
279 let mut worklist = data
282 .map(|child| ModuleId { krate: from.krate, local_id: *child })
283 .collect::<Vec<_>>();
284 let mut parent = data.parent;
285 while let Some(p) = parent {
286 worklist.push(ModuleId { krate: from.krate, local_id: p });
287 parent = def_map.modules[p].parent;
290 let mut seen: FxHashSet<_> = FxHashSet::default();
292 let mut locations = Vec::new();
293 while let Some(module) = worklist.pop() {
294 if !seen.insert(module) {
295 continue; // already processed this module
299 let data = if module.krate == from.krate {
300 &def_map[module.local_id]
302 // The crate might reexport a module defined in another crate.
303 ext_def_map = db.crate_def_map(module.krate);
304 &ext_def_map[module.local_id]
307 if let Some((name, vis)) = data.scope.name_of(item) {
308 if vis.is_visible_from(db, from) {
309 let is_private = if let Visibility::Module(private_to) = vis {
310 private_to.local_id == module.local_id
314 let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
315 data.scope.declarations().any(|it| it == module_def_id)
320 // Ignore private imports. these could be used if we are
321 // in a submodule of this module, but that's usually not
322 // what the user wants; and if this module can import
323 // the item and we're a submodule of it, so can we.
324 // Also this keeps the cached data smaller.
325 if !is_private || is_original_def {
326 locations.push((module, name.clone()));
331 // Descend into all modules visible from `from`.
332 for (_, per_ns) in data.scope.entries() {
333 if let Some((ModuleDefId::ModuleId(module), vis)) = per_ns.take_types_vis() {
334 if vis.is_visible_from(db, from) {
335 worklist.push(module);
346 use base_db::fixture::WithFixture;
347 use hir_expand::hygiene::Hygiene;
348 use syntax::ast::AstNode;
349 use test_utils::mark;
351 use crate::test_db::TestDB;
355 /// `code` needs to contain a cursor marker; checks that `find_path` for the
356 /// item the `path` refers to returns that same path when called from the
357 /// module the cursor is in.
358 fn check_found_path_(ra_fixture: &str, path: &str, absolute: bool) {
359 let (db, pos) = TestDB::with_position(ra_fixture);
360 let module = db.module_for_file(pos.file_id);
361 let parsed_path_file = syntax::SourceFile::parse(&format!("use {};", path));
363 parsed_path_file.syntax_node().descendants().find_map(syntax::ast::Path::cast).unwrap();
364 let mod_path = ModPath::from_src(ast_path, &Hygiene::new_unhygienic()).unwrap();
366 let crate_def_map = db.crate_def_map(module.krate);
367 let resolved = crate_def_map
372 crate::item_scope::BuiltinShadowMode::Module,
378 let found_path = if absolute { find_path_prefixed } else { find_path }(
380 ItemInNs::Types(resolved),
383 assert_eq!(found_path, Some(mod_path), "absolute {}", absolute);
386 fn check_found_path(ra_fixture: &str, path: &str) {
387 check_found_path_(ra_fixture, path, false);
390 fn check_found_path_abs(ra_fixture: &str, path: &str) {
391 check_found_path_(ra_fixture, path, true);
401 check_found_path(code, "S");
402 check_found_path_abs(code, "S");
412 check_found_path(code, "E::A");
413 check_found_path_abs(code, "E::A");
425 check_found_path(code, "foo::S");
426 check_found_path_abs(code, "foo::S");
440 check_found_path(code, "super::S");
441 check_found_path_abs(code, "super::S");
452 check_found_path(code, "self");
453 check_found_path_abs(code, "self");
464 check_found_path(code, "crate");
465 check_found_path_abs(code, "crate");
477 check_found_path(code, "crate::S");
478 check_found_path_abs(code, "crate::S");
482 fn different_crate() {
484 //- /main.rs crate:main deps:std
486 //- /std.rs crate:std
489 check_found_path(code, "std::S");
490 check_found_path_abs(code, "std::S");
494 fn different_crate_renamed() {
496 //- /main.rs crate:main deps:std
497 extern crate std as std_renamed;
499 //- /std.rs crate:std
502 check_found_path(code, "std_renamed::S");
503 check_found_path_abs(code, "std_renamed::S");
507 fn partially_imported() {
508 // Tests that short paths are used even for external items, when parts of the path are
511 //- /main.rs crate:main deps:syntax
516 //- /lib.rs crate:syntax
518 pub enum ModuleItem {
523 check_found_path(code, "ast::ModuleItem");
524 check_found_path_abs(code, "syntax::ast::ModuleItem");
527 //- /main.rs crate:main deps:syntax
531 //- /lib.rs crate:syntax
533 pub enum ModuleItem {
538 check_found_path(code, "syntax::ast::ModuleItem");
539 check_found_path_abs(code, "syntax::ast::ModuleItem");
543 fn same_crate_reexport() {
547 mod foo { pub(super) struct S; }
548 pub(crate) use foo::*;
552 check_found_path(code, "bar::S");
553 check_found_path_abs(code, "bar::S");
557 fn same_crate_reexport_rename() {
561 mod foo { pub(super) struct S; }
562 pub(crate) use foo::S as U;
566 check_found_path(code, "bar::U");
567 check_found_path_abs(code, "bar::U");
571 fn different_crate_reexport() {
573 //- /main.rs crate:main deps:std
575 //- /std.rs crate:std deps:core
577 //- /core.rs crate:core
580 check_found_path(code, "std::S");
581 check_found_path_abs(code, "std::S");
587 //- /main.rs crate:main deps:std
589 //- /std.rs crate:std
590 pub mod prelude { pub struct S; }
594 check_found_path(code, "S");
595 check_found_path_abs(code, "S");
599 fn enum_variant_from_prelude() {
601 //- /main.rs crate:main deps:std
603 //- /std.rs crate:std
605 pub enum Option<T> { Some(T), None }
611 check_found_path(code, "None");
612 check_found_path(code, "Some");
613 check_found_path_abs(code, "None");
614 check_found_path_abs(code, "Some");
626 pub mod bar { pub struct S; }
628 pub use crate::foo::bar::S;
630 check_found_path(code, "baz::S");
631 check_found_path_abs(code, "baz::S");
635 fn discount_private_imports() {
639 pub mod bar { pub struct S; }
644 // crate::S would be shorter, but using private imports seems wrong
645 check_found_path(code, "crate::bar::S");
646 check_found_path_abs(code, "crate::bar::S");
664 check_found_path(code, "crate::foo::S");
665 check_found_path_abs(code, "crate::foo::S");
669 fn prefer_std_paths_over_alloc() {
670 mark::check!(prefer_std_paths);
672 //- /main.rs crate:main deps:alloc,std
675 //- /std.rs crate:std deps:alloc
677 pub use alloc::sync::Arc;
680 //- /zzz.rs crate:alloc
685 check_found_path(code, "std::sync::Arc");
686 check_found_path_abs(code, "std::sync::Arc");
690 fn prefer_core_paths_over_std() {
691 mark::check!(prefer_no_std_paths);
693 //- /main.rs crate:main deps:core,std
698 //- /std.rs crate:std deps:core
701 pub use core::fmt::Error;
704 //- /zzz.rs crate:core
710 check_found_path(code, "core::fmt::Error");
711 check_found_path_abs(code, "core::fmt::Error");
715 fn prefer_alloc_paths_over_std() {
717 //- /main.rs crate:main deps:alloc,std
722 //- /std.rs crate:std deps:alloc
725 pub use alloc::sync::Arc;
728 //- /zzz.rs crate:alloc
734 check_found_path(code, "alloc::sync::Arc");
735 check_found_path_abs(code, "alloc::sync::Arc");
739 fn prefer_shorter_paths_if_not_alloc() {
741 //- /main.rs crate:main deps:megaalloc,std
744 //- /std.rs crate:std deps:megaalloc
746 pub use megaalloc::sync::Arc;
749 //- /zzz.rs crate:megaalloc
752 check_found_path(code, "megaalloc::Arc");
753 check_found_path_abs(code, "megaalloc::Arc");
757 fn builtins_are_in_scope() {
766 check_found_path(code, "u8");
767 check_found_path(code, "u16");
768 check_found_path_abs(code, "u8");
769 check_found_path_abs(code, "u16");