1 //! A map of all publicly exported items in a crate.
3 use std
::{fmt, hash::BuildHasherDefault}
;
6 use fst
::{self, Streamer}
;
7 use hir_expand
::name
::Name
;
8 use indexmap
::{map::Entry, IndexMap}
;
9 use itertools
::Itertools
;
10 use rustc_hash
::{FxHashSet, FxHasher}
;
14 db
::DefDatabase
, item_scope
::ItemInNs
, nameres
::DefMap
, visibility
::Visibility
, AssocItemId
,
15 ModuleDefId
, ModuleId
, TraitId
,
18 type FxIndexMap
<K
, V
> = IndexMap
<K
, V
, BuildHasherDefault
<FxHasher
>>;
20 /// Item import details stored in the `ImportMap`.
21 #[derive(Debug, Clone, Eq, PartialEq)]
22 pub struct ImportInfo
{
23 /// A path that can be used to import the item, relative to the crate's root.
25 /// The module containing this item.
26 pub container
: ModuleId
,
27 /// Whether the import is a trait associated item or not.
28 pub is_trait_assoc_item
: bool
,
31 #[derive(Debug, Clone, Eq, PartialEq)]
32 pub struct ImportPath
{
33 pub segments
: Vec
<Name
>,
37 pub fn display
<'a
>(&'a
self, db
: &'a
dyn DefDatabase
) -> impl fmt
::Display
+ 'a
{
39 db
: &'a
dyn DefDatabase
,
42 impl fmt
::Display
for Display
<'_
> {
43 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
45 &self.path
.segments
.iter().map(|it
| it
.display(self.db
.upcast())).format("::"),
50 Display { db, path: self }
53 fn len(&self) -> usize {
58 /// A map from publicly exported items to the path needed to import/name them from a downstream
61 /// Reexports of items are taken into account, ie. if something is exported under multiple
62 /// names, the one with the shortest import path will be used.
64 /// Note that all paths are relative to the containing crate's root, so the crate name still needs
65 /// to be prepended to the `ModPath` before the path is valid.
67 pub struct ImportMap
{
68 map
: FxIndexMap
<ItemInNs
, ImportInfo
>,
70 /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
71 /// values returned by running `fst`.
73 /// Since a path can refer to multiple items due to namespacing, we store all items with the
74 /// same path right after each other. This allows us to find all items after the FST gives us
75 /// the index of the first one.
76 importables
: Vec
<ItemInNs
>,
77 fst
: fst
::Map
<Vec
<u8>>,
81 pub fn import_map_query(db
: &dyn DefDatabase
, krate
: CrateId
) -> Arc
<Self> {
82 let _p
= profile
::span("import_map_query");
84 let mut import_map
= collect_import_map(db
, krate
);
86 let mut importables
= import_map
89 .map(|(item
, info
)| (item
, fst_path(db
, &info
.path
)))
91 importables
.sort_by(|(_
, fst_path
), (_
, fst_path2
)| fst_path
.cmp(fst_path2
));
93 // Build the FST, taking care not to insert duplicate values.
95 let mut builder
= fst
::MapBuilder
::memory();
96 let mut last_batch_start
= 0;
98 for idx
in 0..importables
.len() {
99 let key
= &importables
[last_batch_start
].1;
100 if let Some((_
, fst_path
)) = importables
.get(idx
+ 1) {
106 let _
= builder
.insert(key
, last_batch_start
as u64);
108 last_batch_start
= idx
+ 1;
111 import_map
.fst
= builder
.into_map();
112 import_map
.importables
= importables
.iter().map(|&(&item
, _
)| item
).collect();
117 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
118 pub fn path_of(&self, item
: ItemInNs
) -> Option
<&ImportPath
> {
119 self.import_info_for(item
).map(|it
| &it
.path
)
122 pub fn import_info_for(&self, item
: ItemInNs
) -> Option
<&ImportInfo
> {
127 fn fmt_for_test(&self, db
: &dyn DefDatabase
) -> String
{
128 let mut importable_paths
: Vec
<_
> = self
131 .map(|(item
, info
)| {
132 let ns
= match item
{
133 ItemInNs
::Types(_
) => "t",
134 ItemInNs
::Values(_
) => "v",
135 ItemInNs
::Macros(_
) => "m",
137 format
!("- {} ({ns})", info
.path
.display(db
))
141 importable_paths
.sort();
142 importable_paths
.join("\n")
145 fn collect_trait_assoc_items(
147 db
: &dyn DefDatabase
,
150 original_import_info
: &ImportInfo
,
152 let _p
= profile
::span("collect_trait_assoc_items");
153 for (assoc_item_name
, item
) in &db
.trait_data(tr
).items
{
154 let module_def_id
= match item
{
155 AssocItemId
::FunctionId(f
) => ModuleDefId
::from(*f
),
156 AssocItemId
::ConstId(c
) => ModuleDefId
::from(*c
),
157 // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
158 // qualifier, ergo no need to store it for imports in import_map
159 AssocItemId
::TypeAliasId(_
) => {
160 cov_mark
::hit
!(type_aliases_ignored
);
164 let assoc_item
= if is_type_in_ns
{
165 ItemInNs
::Types(module_def_id
)
167 ItemInNs
::Values(module_def_id
)
170 let mut assoc_item_info
= original_import_info
.clone();
171 assoc_item_info
.path
.segments
.push(assoc_item_name
.to_owned());
172 assoc_item_info
.is_trait_assoc_item
= true;
173 self.map
.insert(assoc_item
, assoc_item_info
);
178 fn collect_import_map(db
: &dyn DefDatabase
, krate
: CrateId
) -> ImportMap
{
179 let _p
= profile
::span("collect_import_map");
181 let def_map
= db
.crate_def_map(krate
);
182 let mut import_map
= ImportMap
::default();
184 // We look only into modules that are public(ly reexported), starting with the crate root.
185 let empty
= ImportPath { segments: vec![] }
;
186 let root
= def_map
.module_id(DefMap
::ROOT
);
187 let mut worklist
= vec
![(root
, empty
)];
188 while let Some((module
, mod_path
)) = worklist
.pop() {
190 let mod_data
= if module
.krate
== krate
{
191 &def_map
[module
.local_id
]
193 // The crate might reexport a module defined in another crate.
194 ext_def_map
= module
.def_map(db
);
195 &ext_def_map
[module
.local_id
]
198 let visible_items
= mod_data
.scope
.entries().filter_map(|(name
, per_ns
)| {
199 let per_ns
= per_ns
.filter_visibility(|vis
| vis
== Visibility
::Public
);
200 if per_ns
.is_none() { None }
else { Some((name, per_ns)) }
203 for (name
, per_ns
) in visible_items
{
205 let mut path
= mod_path
.clone();
206 path
.segments
.push(name
.clone());
210 for item
in per_ns
.iter_items() {
211 let path
= mk_path();
212 let path_len
= path
.len();
214 ImportInfo { path, container: module, is_trait_assoc_item: false }
;
216 if let Some(ModuleDefId
::TraitId(tr
)) = item
.as_module_def_id() {
217 import_map
.collect_trait_assoc_items(
220 matches
!(item
, ItemInNs
::Types(_
)),
225 match import_map
.map
.entry(item
) {
226 Entry
::Vacant(entry
) => {
227 entry
.insert(import_info
);
229 Entry
::Occupied(mut entry
) => {
230 // If the new path is shorter, prefer that one.
231 if path_len
< entry
.get().path
.len() {
232 *entry
.get_mut() = import_info
;
239 // If we've just added a path to a module, descend into it. We might traverse
240 // modules multiple times, but only if the new path to it is shorter than the
241 // first (else we `continue` above).
242 if let Some(ModuleDefId
::ModuleId(mod_id
)) = item
.as_module_def_id() {
243 worklist
.push((mod_id
, mk_path()));
252 impl PartialEq
for ImportMap
{
253 fn eq(&self, other
: &Self) -> bool
{
254 // `fst` and `importables` are built from `map`, so we don't need to compare them.
255 self.map
== other
.map
259 impl Eq
for ImportMap {}
261 impl fmt
::Debug
for ImportMap
{
262 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
263 let mut importable_paths
: Vec
<_
> = self
266 .map(|(item
, _
)| match item
{
267 ItemInNs
::Types(it
) => format
!("- {it:?} (t)",),
268 ItemInNs
::Values(it
) => format
!("- {it:?} (v)",),
269 ItemInNs
::Macros(it
) => format
!("- {it:?} (m)",),
273 importable_paths
.sort();
274 f
.write_str(&importable_paths
.join("\n"))
278 fn fst_path(db
: &dyn DefDatabase
, path
: &ImportPath
) -> String
{
279 let _p
= profile
::span("fst_path");
280 let mut s
= path
.display(db
).to_string();
281 s
.make_ascii_lowercase();
285 #[derive(Debug, Eq, PartialEq, Hash)]
286 pub enum ImportKind
{
301 /// A way to match import map contents against the search query.
303 pub enum SearchMode
{
304 /// Import map entry should strictly match the query string.
306 /// Import map entry should contain the query string.
308 /// Import map entry should contain all letters from the query string,
309 /// in the same order, but not necessary adjacent.
318 assoc_items_only
: bool
,
319 search_mode
: SearchMode
,
320 case_sensitive
: bool
,
322 exclude_import_kinds
: FxHashSet
<ImportKind
>,
326 pub fn new(query
: String
) -> Self {
327 let lowercased
= query
.to_lowercase();
332 assoc_items_only
: false,
333 search_mode
: SearchMode
::Contains
,
334 case_sensitive
: false,
335 limit
: usize::max_value(),
336 exclude_import_kinds
: FxHashSet
::default(),
340 /// Matches entries' names only, ignoring the rest of
342 /// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
343 pub fn name_only(self) -> Self {
344 Self { name_only: true, ..self }
347 /// Matches only the entries that are associated items, ignoring the rest.
348 pub fn assoc_items_only(self) -> Self {
349 Self { assoc_items_only: true, ..self }
352 /// Specifies the way to search for the entries using the query.
353 pub fn search_mode(self, search_mode
: SearchMode
) -> Self {
354 Self { search_mode, ..self }
357 /// Limits the returned number of items to `limit`.
358 pub fn limit(self, limit
: usize) -> Self {
359 Self { limit, ..self }
362 /// Respect casing of the query string when matching.
363 pub fn case_sensitive(self) -> Self {
364 Self { case_sensitive: true, ..self }
367 /// Do not include imports of the specified kind in the search results.
368 pub fn exclude_import_kind(mut self, import_kind
: ImportKind
) -> Self {
369 self.exclude_import_kinds
.insert(import_kind
);
375 db
: &dyn DefDatabase
,
377 enforce_lowercase
: bool
,
379 let _p
= profile
::span("import_map::Query::import_matches");
380 if import
.is_trait_assoc_item
{
381 if self.exclude_import_kinds
.contains(&ImportKind
::AssociatedItem
) {
384 } else if self.assoc_items_only
{
388 let mut input
= if import
.is_trait_assoc_item
|| self.name_only
{
389 import
.path
.segments
.last().unwrap().display(db
.upcast()).to_string()
391 import
.path
.display(db
).to_string()
393 if enforce_lowercase
|| !self.case_sensitive
{
394 input
.make_ascii_lowercase();
398 if !enforce_lowercase
&& self.case_sensitive { &self.query }
else { &self.lowercased }
;
400 match self.search_mode
{
401 SearchMode
::Equals
=> &input
== query_string
,
402 SearchMode
::Contains
=> input
.contains(query_string
),
403 SearchMode
::Fuzzy
=> {
404 let mut unchecked_query_chars
= query_string
.chars();
405 let mut mismatching_query_char
= unchecked_query_chars
.next();
407 for input_char
in input
.chars() {
408 match mismatching_query_char
{
410 Some(matching_query_char
) if matching_query_char
== input_char
=> {
411 mismatching_query_char
= unchecked_query_chars
.next();
416 mismatching_query_char
.is_none()
422 /// Searches dependencies of `krate` for an importable path matching `query`.
424 /// This returns a list of items that could be imported from dependencies of `krate`.
425 pub fn search_dependencies(
426 db
: &dyn DefDatabase
,
429 ) -> FxHashSet
<ItemInNs
> {
430 let _p
= profile
::span("search_dependencies").detail(|| format
!("{query:?}"));
432 let graph
= db
.crate_graph();
433 let import_maps
: Vec
<_
> =
434 graph
[krate
].dependencies
.iter().map(|dep
| db
.import_map(dep
.crate_id
)).collect();
436 let automaton
= fst
::automaton
::Subsequence
::new(&query
.lowercased
);
438 let mut op
= fst
::map
::OpBuilder
::new();
439 for map
in &import_maps
{
440 op
= op
.add(map
.fst
.search(&automaton
));
443 let mut stream
= op
.union();
445 let mut all_indexed_values
= FxHashSet
::default();
446 while let Some((_
, indexed_values
)) = stream
.next() {
447 all_indexed_values
.extend(indexed_values
.iter().copied());
450 let mut res
= FxHashSet
::default();
451 for indexed_value
in all_indexed_values
{
452 let import_map
= &import_maps
[indexed_value
.index
];
453 let importables
= &import_map
.importables
[indexed_value
.value
as usize..];
455 let common_importable_data
= &import_map
.map
[&importables
[0]];
456 if !query
.import_matches(db
, common_importable_data
, true) {
460 // Path shared by the importable items in this group.
461 let common_importables_path_fst
= fst_path(db
, &common_importable_data
.path
);
462 // Add the items from this `ModPath` group. Those are all subsequent items in
463 // `importables` whose paths match `path`.
464 let iter
= importables
468 common_importables_path_fst
== fst_path(db
, &import_map
.map
[item
].path
)
470 .filter(|&item
| match item_import_kind(item
) {
471 Some(import_kind
) => !query
.exclude_import_kinds
.contains(&import_kind
),
475 !query
.case_sensitive
// we've already checked the common importables path case-insensitively
476 || query
.import_matches(db
, &import_map
.map
[item
], false)
480 if res
.len() >= query
.limit
{
488 fn item_import_kind(item
: ItemInNs
) -> Option
<ImportKind
> {
489 Some(match item
.as_module_def_id()?
{
490 ModuleDefId
::ModuleId(_
) => ImportKind
::Module
,
491 ModuleDefId
::FunctionId(_
) => ImportKind
::Function
,
492 ModuleDefId
::AdtId(_
) => ImportKind
::Adt
,
493 ModuleDefId
::EnumVariantId(_
) => ImportKind
::EnumVariant
,
494 ModuleDefId
::ConstId(_
) => ImportKind
::Const
,
495 ModuleDefId
::StaticId(_
) => ImportKind
::Static
,
496 ModuleDefId
::TraitId(_
) => ImportKind
::Trait
,
497 ModuleDefId
::TraitAliasId(_
) => ImportKind
::TraitAlias
,
498 ModuleDefId
::TypeAliasId(_
) => ImportKind
::TypeAlias
,
499 ModuleDefId
::BuiltinType(_
) => ImportKind
::BuiltinType
,
500 ModuleDefId
::MacroId(_
) => ImportKind
::Macro
,
506 use base_db
::{fixture::WithFixture, SourceDatabase, Upcast}
;
507 use expect_test
::{expect, Expect}
;
509 use crate::{db::DefDatabase, test_db::TestDB, ItemContainerId, Lookup}
;
513 fn check_search(ra_fixture
: &str, crate_name
: &str, query
: Query
, expect
: Expect
) {
514 let db
= TestDB
::with_files(ra_fixture
);
515 let crate_graph
= db
.crate_graph();
516 let krate
= crate_graph
519 crate_graph
[*krate
].display_name
.as_ref().map(|n
| n
.to_string())
520 == Some(crate_name
.to_string())
524 let actual
= search_dependencies(db
.upcast(), krate
, query
)
526 .filter_map(|dependency
| {
527 let dependency_krate
= dependency
.krate(db
.upcast())?
;
528 let dependency_imports
= db
.import_map(dependency_krate
);
530 let (path
, mark
) = match assoc_item_path(&db
, &dependency_imports
, dependency
) {
531 Some(assoc_item_path
) => (assoc_item_path
, "a"),
533 dependency_imports
.path_of(dependency
)?
.display(&db
).to_string(),
535 ItemInNs
::Types(ModuleDefId
::FunctionId(_
))
536 | ItemInNs
::Values(ModuleDefId
::FunctionId(_
)) => "f",
537 ItemInNs
::Types(_
) => "t",
538 ItemInNs
::Values(_
) => "v",
539 ItemInNs
::Macros(_
) => "m",
546 crate_graph
[dependency_krate
].display_name
.as_ref()?
,
551 // HashSet iteration order isn't defined - it's different on
552 // x86_64 and i686 at the very least
554 .collect
::<String
>();
555 expect
.assert_eq(&actual
)
559 db
: &dyn DefDatabase
,
560 dependency_imports
: &ImportMap
,
561 dependency
: ItemInNs
,
562 ) -> Option
<String
> {
563 let dependency_assoc_item_id
= match dependency
{
564 ItemInNs
::Types(ModuleDefId
::FunctionId(id
))
565 | ItemInNs
::Values(ModuleDefId
::FunctionId(id
)) => AssocItemId
::from(id
),
566 ItemInNs
::Types(ModuleDefId
::ConstId(id
))
567 | ItemInNs
::Values(ModuleDefId
::ConstId(id
)) => AssocItemId
::from(id
),
568 ItemInNs
::Types(ModuleDefId
::TypeAliasId(id
))
569 | ItemInNs
::Values(ModuleDefId
::TypeAliasId(id
)) => AssocItemId
::from(id
),
573 let trait_
= assoc_to_trait(db
, dependency
)?
;
574 if let ModuleDefId
::TraitId(tr
) = trait_
.as_module_def_id()?
{
575 let trait_data
= db
.trait_data(tr
);
576 let assoc_item_name
=
577 trait_data
.items
.iter().find_map(|(assoc_item_name
, assoc_item_id
)| {
578 if &dependency_assoc_item_id
== assoc_item_id
{
579 Some(assoc_item_name
)
586 dependency_imports
.path_of(trait_
)?
.display(db
),
587 assoc_item_name
.display(db
.upcast())
593 fn assoc_to_trait(db
: &dyn DefDatabase
, item
: ItemInNs
) -> Option
<ItemInNs
> {
594 let assoc
: AssocItemId
= match item
{
595 ItemInNs
::Types(it
) | ItemInNs
::Values(it
) => match it
{
596 ModuleDefId
::TypeAliasId(it
) => it
.into(),
597 ModuleDefId
::FunctionId(it
) => it
.into(),
598 ModuleDefId
::ConstId(it
) => it
.into(),
604 let container
= match assoc
{
605 AssocItemId
::FunctionId(it
) => it
.lookup(db
).container
,
606 AssocItemId
::ConstId(it
) => it
.lookup(db
).container
,
607 AssocItemId
::TypeAliasId(it
) => it
.lookup(db
).container
,
611 ItemContainerId
::TraitId(it
) => Some(ItemInNs
::Types(it
.into())),
616 fn check(ra_fixture
: &str, expect
: Expect
) {
617 let db
= TestDB
::with_files(ra_fixture
);
618 let crate_graph
= db
.crate_graph();
620 let actual
= crate_graph
622 .filter_map(|krate
| {
623 let cdata
= &crate_graph
[krate
];
624 let name
= cdata
.display_name
.as_ref()?
;
626 let map
= db
.import_map(krate
);
628 Some(format
!("{name}:\n{}\n", map
.fmt_for_test(db
.upcast())))
631 .collect
::<String
>();
633 expect
.assert_eq(&actual
)
640 //- /main.rs crate:main deps:lib
644 pub struct InPrivateModule;
654 pub mod real_pu2 { // same path length as above
658 //- /lib.rs crate:lib
660 pub struct Pub2; // t + v
678 fn prefers_shortest_path() {
681 //- /main.rs crate:main
688 pub use super::sub::subsub::Def;
701 fn type_reexport_cross_crate() {
702 // Reexports need to be visible from a crate, even if the original crate exports the item
703 // at a shorter path.
706 //- /main.rs crate:main deps:lib
710 //- /lib.rs crate:lib
726 fn macro_reexport() {
729 //- /main.rs crate:main deps:lib
731 pub use lib::pub_macro;
733 //- /lib.rs crate:lib
735 macro_rules! pub_macro {
750 fn module_reexport() {
751 // Reexporting modules from a dependency adds all contents to the import map.
754 //- /main.rs crate:main deps:lib
755 pub use lib::module as reexported_module;
756 //- /lib.rs crate:lib
767 - reexported_module (t)
768 - reexported_module::S (t)
769 - reexported_module::S (v)
775 fn cyclic_module_reexport() {
776 // A cyclic reexport does not hang.
779 //- /lib.rs crate:lib
782 pub use super::sub::*;
786 pub use super::module;
803 //- /lib.rs crate:lib
804 macro_rules! private_macro {
819 //- /lib.rs crate:lib
820 pub struct Thing; // t + v
822 macro_rules! Thing { // m
836 //- /lib.rs crate:lib
837 pub mod Thing {} // t
839 macro_rules! Thing { // m
852 fn fuzzy_import_trait_and_assoc_items() {
853 cov_mark
::check
!(type_aliases_ignored
);
855 //- /main.rs crate:main deps:dep
856 //- /dep.rs crate:dep
860 const FMT_CONST: bool;
862 fn format_function();
863 fn format_method(&self);
871 Query
::new("fmt".to_string()).search_mode(SearchMode
::Fuzzy
),
874 dep::fmt::Display (t)
875 dep::fmt::Display::FMT_CONST (a)
876 dep::fmt::Display::format_function (a)
877 dep::fmt::Display::format_method (a)
883 fn assoc_items_filtering() {
885 //- /main.rs crate:main deps:dep
886 //- /dep.rs crate:dep
890 const FMT_CONST: bool;
892 fn format_function();
893 fn format_method(&self);
901 Query
::new("fmt".to_string()).search_mode(SearchMode
::Fuzzy
).assoc_items_only(),
903 dep::fmt::Display::FMT_CONST (a)
904 dep::fmt::Display::format_function (a)
905 dep::fmt::Display::format_method (a)
912 Query
::new("fmt".to_string())
913 .search_mode(SearchMode
::Fuzzy
)
914 .exclude_import_kind(ImportKind
::AssociatedItem
),
917 dep::fmt::Display (t)
924 Query
::new("fmt".to_string())
925 .search_mode(SearchMode
::Fuzzy
)
927 .exclude_import_kind(ImportKind
::AssociatedItem
),
935 //- /main.rs crate:main deps:dep
936 //- /dep.rs crate:dep deps:tdep
937 use tdep::fmt as fmt_dep;
952 //- /tdep.rs crate:tdep
954 pub struct NotImportableFromMain;
961 Query
::new("fmt".to_string()).search_mode(SearchMode
::Fuzzy
),
967 dep::fmt::Display (t)
968 dep::fmt::Display::fmt (a)
976 Query
::new("fmt".to_string()).search_mode(SearchMode
::Equals
),
982 dep::fmt::Display::fmt (a)
989 Query
::new("fmt".to_string()).search_mode(SearchMode
::Contains
),
995 dep::fmt::Display (t)
996 dep::fmt::Display::fmt (a)
1003 let ra_fixture
= r
#"
1004 //- /main.rs crate:main deps:dep
1005 //- /dep.rs crate:dep deps:tdep
1006 use tdep::fmt as fmt_dep;
1021 //- /tdep.rs crate:tdep
1023 pub struct NotImportableFromMain;
1030 Query
::new("fmt".to_string()),
1036 dep::fmt::Display (t)
1037 dep::fmt::Display::fmt (a)
1044 Query
::new("fmt".to_string()).name_only(),
1050 dep::fmt::Display::fmt (a)
1056 fn search_casing() {
1057 let ra_fixture
= r
#"
1058 //- /main.rs crate:main deps:dep
1059 //- /dep.rs crate:dep
1068 Query
::new("FMT".to_string()),
1080 Query
::new("FMT".to_string()).case_sensitive(),
1092 //- /main.rs crate:main deps:dep
1093 //- /dep.rs crate:dep
1109 Query
::new("".to_string()).limit(2),
1120 fn search_exclusions() {
1121 let ra_fixture
= r
#"
1122 //- /main.rs crate:main deps:dep
1123 //- /dep.rs crate:dep
1132 Query
::new("FMT".to_string()),
1144 Query
::new("FMT".to_string()).exclude_import_kind(ImportKind
::Adt
),