]> git.lizzy.rs Git - rust.git/blob - crates/hir_def/src/find_path.rs
Merge #6073
[rust.git] / crates / hir_def / src / find_path.rs
1 //! An algorithm to find a path to refer to a certain item.
2
3 use hir_expand::name::{known, AsName, Name};
4 use rustc_hash::FxHashSet;
5 use test_utils::mark;
6
7 use crate::nameres::CrateDefMap;
8 use crate::{
9     db::DefDatabase,
10     item_scope::ItemInNs,
11     path::{ModPath, PathKind},
12     visibility::Visibility,
13     ModuleDefId, ModuleId,
14 };
15
16 // FIXME: handle local items
17
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)
23 }
24
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)
28 }
29
30 const MAX_PATH_LEN: usize = 15;
31
32 impl ModPath {
33     fn starts_with_std(&self) -> bool {
34         self.segments.first() == Some(&known::std)
35     }
36
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)
42     }
43 }
44
45 fn check_crate_self_super(
46     def_map: &CrateDefMap,
47     item: ItemInNs,
48     from: ModuleId,
49 ) -> Option<ModPath> {
50     // - if the item is the crate root, return `crate`
51     if item
52         == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
53             krate: from.krate,
54             local_id: def_map.root,
55         }))
56     {
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()))
61     } else {
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)
64             if item
65                 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
66                     krate: from.krate,
67                     local_id: parent_id,
68                 }))
69             {
70                 return Some(ModPath::from_segments(PathKind::Super(1), Vec::new()));
71             }
72         }
73         None
74     }
75 }
76
77 #[derive(Copy, Clone, PartialEq, Eq)]
78 pub enum Prefixed {
79     Not,
80     BySelf,
81     Plain,
82 }
83
84 impl Prefixed {
85     #[inline]
86     fn prefix(self) -> Option<PathKind> {
87         match self {
88             Prefixed::Not => None,
89             Prefixed::BySelf => Some(PathKind::Super(0)),
90             Prefixed::Plain => Some(PathKind::Plain),
91         }
92     }
93
94     #[inline]
95     fn prefixed(self) -> bool {
96         self != Prefixed::Not
97     }
98 }
99
100 fn find_path_inner(
101     db: &dyn DefDatabase,
102     item: ItemInNs,
103     from: ModuleId,
104     max_len: usize,
105     prefixed: Prefixed,
106 ) -> Option<ModPath> {
107     if max_len == 0 {
108         return None;
109     }
110
111     // Base cases:
112
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;
116     let scope_name =
117         if let Some((name, _)) = from_scope.name_of(item) { Some(name.clone()) } else { None };
118     if !prefixed.prefixed() && scope_name.is_some() {
119         return scope_name
120             .map(|scope_name| ModPath::from_segments(PathKind::Plain, vec![scope_name]));
121     }
122
123     if let modpath @ Some(_) = check_crate_self_super(&def_map, item, from) {
124         return modpath;
125     }
126
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]));
132         }
133     }
134
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()]));
143             }
144         }
145     }
146
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()]));
150     }
151
152     // Recursive case:
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());
158             return Some(path);
159         }
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
162         // variant somewhere
163     }
164
165     // - otherwise, look for modules containing (reexporting) it and import it from one of those
166
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;
172
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.
176
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(
180                 db,
181                 ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
182                 from,
183                 best_path_len - 1,
184                 prefixed,
185             ) {
186                 path.segments.push(name);
187
188                 let new_path = if let Some(best_path) = best_path {
189                     select_best_path(best_path, path, prefer_no_std)
190                 } else {
191                     path
192                 };
193                 best_path_len = new_path.len();
194                 best_path = Some(new_path);
195             }
196         }
197     } else {
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.
201
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(
208                     db,
209                     ItemInNs::Types(ModuleDefId::ModuleId(info.container)),
210                     from,
211                     best_path_len - 1,
212                     prefixed,
213                 )?;
214                 path.segments.push(info.path.segments.last().unwrap().clone());
215                 Some(path)
216             })
217         });
218
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)
222             } else {
223                 path
224             };
225             best_path = Some(new_path);
226         }
227     }
228
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]))
232         })
233     } else {
234         best_path
235     }
236 }
237
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() {
240         if prefer_no_std {
241             mark::hit!(prefer_no_std_paths);
242             new_path
243         } else {
244             mark::hit!(prefer_std_paths);
245             old_path
246         }
247     } else if new_path.starts_with_std() && old_path.can_start_with_std() {
248         if prefer_no_std {
249             mark::hit!(prefer_no_std_paths);
250             old_path
251         } else {
252             mark::hit!(prefer_std_paths);
253             new_path
254         }
255     } else if new_path.len() < old_path.len() {
256         new_path
257     } else {
258         old_path
259     }
260 }
261
262 /// Finds locations in `from.krate` from which `item` can be imported by `from`.
263 fn find_local_import_locations(
264     db: &dyn DefDatabase,
265     item: ItemInNs,
266     from: ModuleId,
267 ) -> Vec<(ModuleId, Name)> {
268     let _p = profile::span("find_local_import_locations");
269
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).
273
274     let def_map = db.crate_def_map(from.krate);
275
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
280         .children
281         .values()
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;
288     }
289
290     let mut seen: FxHashSet<_> = FxHashSet::default();
291
292     let mut locations = Vec::new();
293     while let Some(module) = worklist.pop() {
294         if !seen.insert(module) {
295             continue; // already processed this module
296         }
297
298         let ext_def_map;
299         let data = if module.krate == from.krate {
300             &def_map[module.local_id]
301         } else {
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]
305         };
306
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
311                 } else {
312                     false
313                 };
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)
316                 } else {
317                     false
318                 };
319
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()));
327                 }
328             }
329         }
330
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);
336                 }
337             }
338         }
339     }
340
341     locations
342 }
343
344 #[cfg(test)]
345 mod tests {
346     use base_db::fixture::WithFixture;
347     use hir_expand::hygiene::Hygiene;
348     use syntax::ast::AstNode;
349     use test_utils::mark;
350
351     use crate::test_db::TestDB;
352
353     use super::*;
354
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));
362         let ast_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();
365
366         let crate_def_map = db.crate_def_map(module.krate);
367         let resolved = crate_def_map
368             .resolve_path(
369                 &db,
370                 module.local_id,
371                 &mod_path,
372                 crate::item_scope::BuiltinShadowMode::Module,
373             )
374             .0
375             .take_types()
376             .unwrap();
377
378         let found_path = if absolute { find_path_prefixed } else { find_path }(
379             &db,
380             ItemInNs::Types(resolved),
381             module,
382         );
383         assert_eq!(found_path, Some(mod_path), "absolute {}", absolute);
384     }
385
386     fn check_found_path(ra_fixture: &str, path: &str) {
387         check_found_path_(ra_fixture, path, false);
388     }
389
390     fn check_found_path_abs(ra_fixture: &str, path: &str) {
391         check_found_path_(ra_fixture, path, true);
392     }
393
394     #[test]
395     fn same_module() {
396         let code = r#"
397             //- /main.rs
398             struct S;
399             <|>
400         "#;
401         check_found_path(code, "S");
402         check_found_path_abs(code, "S");
403     }
404
405     #[test]
406     fn enum_variant() {
407         let code = r#"
408             //- /main.rs
409             enum E { A }
410             <|>
411         "#;
412         check_found_path(code, "E::A");
413         check_found_path_abs(code, "E::A");
414     }
415
416     #[test]
417     fn sub_module() {
418         let code = r#"
419             //- /main.rs
420             mod foo {
421                 pub struct S;
422             }
423             <|>
424         "#;
425         check_found_path(code, "foo::S");
426         check_found_path_abs(code, "foo::S");
427     }
428
429     #[test]
430     fn super_module() {
431         let code = r#"
432             //- /main.rs
433             mod foo;
434             //- /foo.rs
435             mod bar;
436             struct S;
437             //- /foo/bar.rs
438             <|>
439         "#;
440         check_found_path(code, "super::S");
441         check_found_path_abs(code, "super::S");
442     }
443
444     #[test]
445     fn self_module() {
446         let code = r#"
447             //- /main.rs
448             mod foo;
449             //- /foo.rs
450             <|>
451         "#;
452         check_found_path(code, "self");
453         check_found_path_abs(code, "self");
454     }
455
456     #[test]
457     fn crate_root() {
458         let code = r#"
459             //- /main.rs
460             mod foo;
461             //- /foo.rs
462             <|>
463         "#;
464         check_found_path(code, "crate");
465         check_found_path_abs(code, "crate");
466     }
467
468     #[test]
469     fn same_crate() {
470         let code = r#"
471             //- /main.rs
472             mod foo;
473             struct S;
474             //- /foo.rs
475             <|>
476         "#;
477         check_found_path(code, "crate::S");
478         check_found_path_abs(code, "crate::S");
479     }
480
481     #[test]
482     fn different_crate() {
483         let code = r#"
484             //- /main.rs crate:main deps:std
485             <|>
486             //- /std.rs crate:std
487             pub struct S;
488         "#;
489         check_found_path(code, "std::S");
490         check_found_path_abs(code, "std::S");
491     }
492
493     #[test]
494     fn different_crate_renamed() {
495         let code = r#"
496             //- /main.rs crate:main deps:std
497             extern crate std as std_renamed;
498             <|>
499             //- /std.rs crate:std
500             pub struct S;
501         "#;
502         check_found_path(code, "std_renamed::S");
503         check_found_path_abs(code, "std_renamed::S");
504     }
505
506     #[test]
507     fn partially_imported() {
508         // Tests that short paths are used even for external items, when parts of the path are
509         // already in scope.
510         let code = r#"
511             //- /main.rs crate:main deps:syntax
512
513             use syntax::ast;
514             <|>
515
516             //- /lib.rs crate:syntax
517             pub mod ast {
518                 pub enum ModuleItem {
519                     A, B, C,
520                 }
521             }
522         "#;
523         check_found_path(code, "ast::ModuleItem");
524         check_found_path_abs(code, "syntax::ast::ModuleItem");
525
526         let code = r#"
527             //- /main.rs crate:main deps:syntax
528
529             <|>
530
531             //- /lib.rs crate:syntax
532             pub mod ast {
533                 pub enum ModuleItem {
534                     A, B, C,
535                 }
536             }
537         "#;
538         check_found_path(code, "syntax::ast::ModuleItem");
539         check_found_path_abs(code, "syntax::ast::ModuleItem");
540     }
541
542     #[test]
543     fn same_crate_reexport() {
544         let code = r#"
545             //- /main.rs
546             mod bar {
547                 mod foo { pub(super) struct S; }
548                 pub(crate) use foo::*;
549             }
550             <|>
551         "#;
552         check_found_path(code, "bar::S");
553         check_found_path_abs(code, "bar::S");
554     }
555
556     #[test]
557     fn same_crate_reexport_rename() {
558         let code = r#"
559             //- /main.rs
560             mod bar {
561                 mod foo { pub(super) struct S; }
562                 pub(crate) use foo::S as U;
563             }
564             <|>
565         "#;
566         check_found_path(code, "bar::U");
567         check_found_path_abs(code, "bar::U");
568     }
569
570     #[test]
571     fn different_crate_reexport() {
572         let code = r#"
573             //- /main.rs crate:main deps:std
574             <|>
575             //- /std.rs crate:std deps:core
576             pub use core::S;
577             //- /core.rs crate:core
578             pub struct S;
579         "#;
580         check_found_path(code, "std::S");
581         check_found_path_abs(code, "std::S");
582     }
583
584     #[test]
585     fn prelude() {
586         let code = r#"
587             //- /main.rs crate:main deps:std
588             <|>
589             //- /std.rs crate:std
590             pub mod prelude { pub struct S; }
591             #[prelude_import]
592             pub use prelude::*;
593         "#;
594         check_found_path(code, "S");
595         check_found_path_abs(code, "S");
596     }
597
598     #[test]
599     fn enum_variant_from_prelude() {
600         let code = r#"
601             //- /main.rs crate:main deps:std
602             <|>
603             //- /std.rs crate:std
604             pub mod prelude {
605                 pub enum Option<T> { Some(T), None }
606                 pub use Option::*;
607             }
608             #[prelude_import]
609             pub use prelude::*;
610         "#;
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");
615     }
616
617     #[test]
618     fn shortest_path() {
619         let code = r#"
620             //- /main.rs
621             pub mod foo;
622             pub mod baz;
623             struct S;
624             <|>
625             //- /foo.rs
626             pub mod bar { pub struct S; }
627             //- /baz.rs
628             pub use crate::foo::bar::S;
629         "#;
630         check_found_path(code, "baz::S");
631         check_found_path_abs(code, "baz::S");
632     }
633
634     #[test]
635     fn discount_private_imports() {
636         let code = r#"
637             //- /main.rs
638             mod foo;
639             pub mod bar { pub struct S; }
640             use bar::S;
641             //- /foo.rs
642             <|>
643         "#;
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");
647     }
648
649     #[test]
650     fn import_cycle() {
651         let code = r#"
652             //- /main.rs
653             pub mod foo;
654             pub mod bar;
655             pub mod baz;
656             //- /bar.rs
657             <|>
658             //- /foo.rs
659             pub use super::baz;
660             pub struct S;
661             //- /baz.rs
662             pub use super::foo;
663         "#;
664         check_found_path(code, "crate::foo::S");
665         check_found_path_abs(code, "crate::foo::S");
666     }
667
668     #[test]
669     fn prefer_std_paths_over_alloc() {
670         mark::check!(prefer_std_paths);
671         let code = r#"
672         //- /main.rs crate:main deps:alloc,std
673         <|>
674
675         //- /std.rs crate:std deps:alloc
676         pub mod sync {
677             pub use alloc::sync::Arc;
678         }
679
680         //- /zzz.rs crate:alloc
681         pub mod sync {
682             pub struct Arc;
683         }
684         "#;
685         check_found_path(code, "std::sync::Arc");
686         check_found_path_abs(code, "std::sync::Arc");
687     }
688
689     #[test]
690     fn prefer_core_paths_over_std() {
691         mark::check!(prefer_no_std_paths);
692         let code = r#"
693         //- /main.rs crate:main deps:core,std
694         #![no_std]
695
696         <|>
697
698         //- /std.rs crate:std deps:core
699
700         pub mod fmt {
701             pub use core::fmt::Error;
702         }
703
704         //- /zzz.rs crate:core
705
706         pub mod fmt {
707             pub struct Error;
708         }
709         "#;
710         check_found_path(code, "core::fmt::Error");
711         check_found_path_abs(code, "core::fmt::Error");
712     }
713
714     #[test]
715     fn prefer_alloc_paths_over_std() {
716         let code = r#"
717         //- /main.rs crate:main deps:alloc,std
718         #![no_std]
719
720         <|>
721
722         //- /std.rs crate:std deps:alloc
723
724         pub mod sync {
725             pub use alloc::sync::Arc;
726         }
727
728         //- /zzz.rs crate:alloc
729
730         pub mod sync {
731             pub struct Arc;
732         }
733         "#;
734         check_found_path(code, "alloc::sync::Arc");
735         check_found_path_abs(code, "alloc::sync::Arc");
736     }
737
738     #[test]
739     fn prefer_shorter_paths_if_not_alloc() {
740         let code = r#"
741         //- /main.rs crate:main deps:megaalloc,std
742         <|>
743
744         //- /std.rs crate:std deps:megaalloc
745         pub mod sync {
746             pub use megaalloc::sync::Arc;
747         }
748
749         //- /zzz.rs crate:megaalloc
750         pub struct Arc;
751         "#;
752         check_found_path(code, "megaalloc::Arc");
753         check_found_path_abs(code, "megaalloc::Arc");
754     }
755
756     #[test]
757     fn builtins_are_in_scope() {
758         let code = r#"
759         //- /main.rs
760         <|>
761
762         pub mod primitive {
763             pub use u8;
764         }
765         "#;
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");
770     }
771 }