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