aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_def/src/import_map.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_def/src/import_map.rs')
-rw-r--r--crates/ra_hir_def/src/import_map.rs323
1 files changed, 323 insertions, 0 deletions
diff --git a/crates/ra_hir_def/src/import_map.rs b/crates/ra_hir_def/src/import_map.rs
new file mode 100644
index 000000000..7dae64efa
--- /dev/null
+++ b/crates/ra_hir_def/src/import_map.rs
@@ -0,0 +1,323 @@
1//! A map of all publicly exported items in a crate.
2
3use crate::{
4 db::DefDatabase,
5 item_scope::ItemInNs,
6 path::{ModPath, PathKind},
7 visibility::Visibility,
8 ModuleDefId, ModuleId,
9};
10use ra_db::CrateId;
11use rustc_hash::FxHashMap;
12use std::{collections::hash_map::Entry, sync::Arc};
13
14/// A map from publicly exported items to the path needed to import/name them from a downstream
15/// crate.
16///
17/// Reexports of items are taken into account, ie. if something is exported under multiple
18/// names, the one with the shortest import path will be used.
19///
20/// Note that all paths are relative to the containing crate's root, so the crate name still needs
21/// to be prepended to the `ModPath` before the path is valid.
22#[derive(Debug, Eq, PartialEq)]
23pub struct ImportMap {
24 map: FxHashMap<ItemInNs, ModPath>,
25}
26
27impl ImportMap {
28 pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
29 let _p = ra_prof::profile("import_map_query");
30 let def_map = db.crate_def_map(krate);
31 let mut import_map = FxHashMap::with_capacity_and_hasher(64, Default::default());
32
33 // We look only into modules that are public(ly reexported), starting with the crate root.
34 let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
35 let root = ModuleId { krate, local_id: def_map.root };
36 let mut worklist = vec![(root, empty)];
37 while let Some((module, mod_path)) = worklist.pop() {
38 let ext_def_map;
39 let mod_data = if module.krate == krate {
40 &def_map[module.local_id]
41 } else {
42 // The crate might reexport a module defined in another crate.
43 ext_def_map = db.crate_def_map(module.krate);
44 &ext_def_map[module.local_id]
45 };
46
47 let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
48 let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
49 if per_ns.is_none() {
50 None
51 } else {
52 Some((name, per_ns))
53 }
54 });
55
56 for (name, per_ns) in visible_items {
57 let mk_path = || {
58 let mut path = mod_path.clone();
59 path.segments.push(name.clone());
60 path
61 };
62
63 for item in per_ns.iter_items() {
64 let path = mk_path();
65 match import_map.entry(item) {
66 Entry::Vacant(entry) => {
67 entry.insert(path);
68 }
69 Entry::Occupied(mut entry) => {
70 // If the new path is shorter, prefer that one.
71 if path.len() < entry.get().len() {
72 *entry.get_mut() = path;
73 } else {
74 continue;
75 }
76 }
77 }
78
79 // If we've just added a path to a module, descend into it.
80 if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
81 worklist.push((mod_id, mk_path()));
82 }
83 }
84 }
85 }
86
87 Arc::new(Self { map: import_map })
88 }
89
90 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
91 pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> {
92 self.map.get(&item)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::test_db::TestDB;
100 use insta::assert_snapshot;
101 use ra_db::fixture::WithFixture;
102 use ra_db::SourceDatabase;
103
104 fn import_map(ra_fixture: &str) -> String {
105 let db = TestDB::with_files(ra_fixture);
106 let crate_graph = db.crate_graph();
107
108 let import_maps: Vec<_> = crate_graph
109 .iter()
110 .filter_map(|krate| {
111 let cdata = &crate_graph[krate];
112 let name = cdata.display_name.as_ref()?;
113
114 let map = db.import_map(krate);
115
116 let mut importable_paths: Vec<_> = map
117 .map
118 .iter()
119 .map(|(item, modpath)| {
120 let ns = match item {
121 ItemInNs::Types(_) => "t",
122 ItemInNs::Values(_) => "v",
123 ItemInNs::Macros(_) => "m",
124 };
125 format!("- {} ({})", modpath, ns)
126 })
127 .collect();
128
129 importable_paths.sort();
130 let importable_paths = importable_paths.join("\n");
131
132 Some(format!("{}:\n{}", name, importable_paths))
133 })
134 .collect();
135
136 import_maps.join("\n")
137 }
138
139 #[test]
140 fn smoke() {
141 let map = import_map(
142 r"
143 //- /main.rs crate:main deps:lib
144
145 mod private {
146 pub use lib::Pub;
147 pub struct InPrivateModule;
148 }
149
150 pub mod publ1 {
151 use lib::Pub;
152 }
153
154 pub mod real_pub {
155 pub use lib::Pub;
156 }
157 pub mod real_pu2 { // same path length as above
158 pub use lib::Pub;
159 }
160
161 //- /lib.rs crate:lib
162 pub struct Pub {}
163 pub struct Pub2; // t + v
164 struct Priv;
165 ",
166 );
167
168 assert_snapshot!(map, @r###"
169 main:
170 - publ1 (t)
171 - real_pu2 (t)
172 - real_pub (t)
173 - real_pub::Pub (t)
174 lib:
175 - Pub (t)
176 - Pub2 (t)
177 - Pub2 (v)
178 "###);
179 }
180
181 #[test]
182 fn prefers_shortest_path() {
183 let map = import_map(
184 r"
185 //- /main.rs crate:main
186
187 pub mod sub {
188 pub mod subsub {
189 pub struct Def {}
190 }
191
192 pub use super::sub::subsub::Def;
193 }
194 ",
195 );
196
197 assert_snapshot!(map, @r###"
198 main:
199 - sub (t)
200 - sub::Def (t)
201 - sub::subsub (t)
202 "###);
203 }
204
205 #[test]
206 fn type_reexport_cross_crate() {
207 // Reexports need to be visible from a crate, even if the original crate exports the item
208 // at a shorter path.
209 let map = import_map(
210 r"
211 //- /main.rs crate:main deps:lib
212 pub mod m {
213 pub use lib::S;
214 }
215 //- /lib.rs crate:lib
216 pub struct S;
217 ",
218 );
219
220 assert_snapshot!(map, @r###"
221 main:
222 - m (t)
223 - m::S (t)
224 - m::S (v)
225 lib:
226 - S (t)
227 - S (v)
228 "###);
229 }
230
231 #[test]
232 fn macro_reexport() {
233 let map = import_map(
234 r"
235 //- /main.rs crate:main deps:lib
236 pub mod m {
237 pub use lib::pub_macro;
238 }
239 //- /lib.rs crate:lib
240 #[macro_export]
241 macro_rules! pub_macro {
242 () => {};
243 }
244 ",
245 );
246
247 assert_snapshot!(map, @r###"
248 main:
249 - m (t)
250 - m::pub_macro (m)
251 lib:
252 - pub_macro (m)
253 "###);
254 }
255
256 #[test]
257 fn module_reexport() {
258 // Reexporting modules from a dependency adds all contents to the import map.
259 let map = import_map(
260 r"
261 //- /main.rs crate:main deps:lib
262 pub use lib::module as reexported_module;
263 //- /lib.rs crate:lib
264 pub mod module {
265 pub struct S;
266 }
267 ",
268 );
269
270 assert_snapshot!(map, @r###"
271 main:
272 - reexported_module (t)
273 - reexported_module::S (t)
274 - reexported_module::S (v)
275 lib:
276 - module (t)
277 - module::S (t)
278 - module::S (v)
279 "###);
280 }
281
282 #[test]
283 fn cyclic_module_reexport() {
284 // Reexporting modules from a dependency adds all contents to the import map.
285 let map = import_map(
286 r"
287 //- /lib.rs crate:lib
288 pub mod module {
289 pub struct S;
290 pub use super::sub::*;
291 }
292
293 pub mod sub {
294 pub use super::module;
295 }
296 ",
297 );
298
299 assert_snapshot!(map, @r###"
300 lib:
301 - module (t)
302 - module::S (t)
303 - module::S (v)
304 - sub (t)
305 "###);
306 }
307
308 #[test]
309 fn private_macro() {
310 let map = import_map(
311 r"
312 //- /lib.rs crate:lib
313 macro_rules! private_macro {
314 () => {};
315 }
316 ",
317 );
318
319 assert_snapshot!(map, @r###"
320 lib:
321 "###);
322 }
323}