diff options
Diffstat (limited to 'crates/ra_hir_def/src/import_map.rs')
-rw-r--r-- | crates/ra_hir_def/src/import_map.rs | 331 |
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 | |||
3 | use std::{collections::hash_map::Entry, fmt, sync::Arc}; | ||
4 | |||
5 | use ra_db::CrateId; | ||
6 | use rustc_hash::FxHashMap; | ||
7 | |||
8 | use 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)] | ||
25 | pub struct ImportMap { | ||
26 | map: FxHashMap<ItemInNs, ModPath>, | ||
27 | } | ||
28 | |||
29 | impl 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 | |||
100 | impl 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)] | ||
121 | mod 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 | } | ||