diff options
author | Jonas Schievink <[email protected]> | 2020-05-20 22:51:20 +0100 |
---|---|---|
committer | Jonas Schievink <[email protected]> | 2020-06-04 18:33:01 +0100 |
commit | d08c63cb9e3574fa97374a8529136814530bf416 (patch) | |
tree | 385c05e8531fccb567ca7ca688de761f4a08edb3 /crates/ra_hir_def | |
parent | c19496f845a4adcd7e0f48f5dcb5b405bbc63dfc (diff) |
Add an ImportMap
Diffstat (limited to 'crates/ra_hir_def')
-rw-r--r-- | crates/ra_hir_def/src/db.rs | 4 | ||||
-rw-r--r-- | crates/ra_hir_def/src/find_path.rs | 19 | ||||
-rw-r--r-- | crates/ra_hir_def/src/import_map.rs | 323 | ||||
-rw-r--r-- | crates/ra_hir_def/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_def/src/path.rs | 13 | ||||
-rw-r--r-- | crates/ra_hir_def/src/per_ns.rs | 10 |
6 files changed, 358 insertions, 12 deletions
diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs index 945a0025e..a23d65371 100644 --- a/crates/ra_hir_def/src/db.rs +++ b/crates/ra_hir_def/src/db.rs | |||
@@ -14,6 +14,7 @@ use crate::{ | |||
14 | docs::Documentation, | 14 | docs::Documentation, |
15 | find_path, | 15 | find_path, |
16 | generics::GenericParams, | 16 | generics::GenericParams, |
17 | import_map::ImportMap, | ||
17 | item_scope::ItemInNs, | 18 | item_scope::ItemInNs, |
18 | lang_item::{LangItemTarget, LangItems}, | 19 | lang_item::{LangItemTarget, LangItems}, |
19 | nameres::{raw::RawItems, CrateDefMap}, | 20 | nameres::{raw::RawItems, CrateDefMap}, |
@@ -122,6 +123,9 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { | |||
122 | 123 | ||
123 | #[salsa::invoke(find_path::find_path_inner_query)] | 124 | #[salsa::invoke(find_path::find_path_inner_query)] |
124 | fn find_path_inner(&self, item: ItemInNs, from: ModuleId, max_len: usize) -> Option<ModPath>; | 125 | fn find_path_inner(&self, item: ItemInNs, from: ModuleId, max_len: usize) -> Option<ModPath>; |
126 | |||
127 | #[salsa::invoke(ImportMap::import_map_query)] | ||
128 | fn import_map(&self, krate: CrateId) -> Arc<ImportMap>; | ||
125 | } | 129 | } |
126 | 130 | ||
127 | fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { | 131 | fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { |
diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs index 4db798473..088e8dd32 100644 --- a/crates/ra_hir_def/src/find_path.rs +++ b/crates/ra_hir_def/src/find_path.rs | |||
@@ -36,17 +36,6 @@ impl ModPath { | |||
36 | let first_segment = self.segments.first(); | 36 | let first_segment = self.segments.first(); |
37 | first_segment == Some(&known::alloc) || first_segment == Some(&known::core) | 37 | first_segment == Some(&known::alloc) || first_segment == Some(&known::core) |
38 | } | 38 | } |
39 | |||
40 | fn len(&self) -> usize { | ||
41 | self.segments.len() | ||
42 | + match self.kind { | ||
43 | PathKind::Plain => 0, | ||
44 | PathKind::Super(i) => i as usize, | ||
45 | PathKind::Crate => 1, | ||
46 | PathKind::Abs => 0, | ||
47 | PathKind::DollarCrate(_) => 1, | ||
48 | } | ||
49 | } | ||
50 | } | 39 | } |
51 | 40 | ||
52 | pub(crate) fn find_path_inner_query( | 41 | pub(crate) fn find_path_inner_query( |
@@ -192,9 +181,17 @@ fn find_importable_locations( | |||
192 | ) -> Vec<(ModuleId, Name)> { | 181 | ) -> Vec<(ModuleId, Name)> { |
193 | let crate_graph = db.crate_graph(); | 182 | let crate_graph = db.crate_graph(); |
194 | let mut result = Vec::new(); | 183 | let mut result = Vec::new(); |
184 | |||
195 | // We only look in the crate from which we are importing, and the direct | 185 | // We only look in the crate from which we are importing, and the direct |
196 | // dependencies. We cannot refer to names from transitive dependencies | 186 | // dependencies. We cannot refer to names from transitive dependencies |
197 | // directly (only through reexports in direct dependencies). | 187 | // directly (only through reexports in direct dependencies). |
188 | |||
189 | // For the crate from which we're importing, we have to check whether any | ||
190 | // module visible to `from` exports the item we're looking for. | ||
191 | // For dependencies of the crate only `pub` items reachable through `pub` | ||
192 | // modules from the crate root are relevant. For that we precompute an | ||
193 | // import map that tells us the shortest path to any importable item with a | ||
194 | // single lookup. | ||
198 | for krate in Some(from.krate) | 195 | for krate in Some(from.krate) |
199 | .into_iter() | 196 | .into_iter() |
200 | .chain(crate_graph[from.krate].dependencies.iter().map(|dep| dep.crate_id)) | 197 | .chain(crate_graph[from.krate].dependencies.iter().map(|dep| dep.crate_id)) |
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 | |||
3 | use crate::{ | ||
4 | db::DefDatabase, | ||
5 | item_scope::ItemInNs, | ||
6 | path::{ModPath, PathKind}, | ||
7 | visibility::Visibility, | ||
8 | ModuleDefId, ModuleId, | ||
9 | }; | ||
10 | use ra_db::CrateId; | ||
11 | use rustc_hash::FxHashMap; | ||
12 | use 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)] | ||
23 | pub struct ImportMap { | ||
24 | map: FxHashMap<ItemInNs, ModPath>, | ||
25 | } | ||
26 | |||
27 | impl 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)] | ||
97 | mod 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 | } | ||
diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index 5325a2760..de490fcc5 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs | |||
@@ -43,6 +43,7 @@ pub mod child_by_source; | |||
43 | 43 | ||
44 | pub mod visibility; | 44 | pub mod visibility; |
45 | pub mod find_path; | 45 | pub mod find_path; |
46 | pub mod import_map; | ||
46 | 47 | ||
47 | #[cfg(test)] | 48 | #[cfg(test)] |
48 | mod test_db; | 49 | mod test_db; |
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs index 4512448e0..bfa921de2 100644 --- a/crates/ra_hir_def/src/path.rs +++ b/crates/ra_hir_def/src/path.rs | |||
@@ -76,6 +76,19 @@ impl ModPath { | |||
76 | } | 76 | } |
77 | } | 77 | } |
78 | 78 | ||
79 | /// Returns the number of segments in the path (counting special segments like `$crate` and | ||
80 | /// `super`). | ||
81 | pub fn len(&self) -> usize { | ||
82 | self.segments.len() | ||
83 | + match self.kind { | ||
84 | PathKind::Plain => 0, | ||
85 | PathKind::Super(i) => i as usize, | ||
86 | PathKind::Crate => 1, | ||
87 | PathKind::Abs => 0, | ||
88 | PathKind::DollarCrate(_) => 1, | ||
89 | } | ||
90 | } | ||
91 | |||
79 | pub fn is_ident(&self) -> bool { | 92 | pub fn is_ident(&self) -> bool { |
80 | self.kind == PathKind::Plain && self.segments.len() == 1 | 93 | self.kind == PathKind::Plain && self.segments.len() == 1 |
81 | } | 94 | } |
diff --git a/crates/ra_hir_def/src/per_ns.rs b/crates/ra_hir_def/src/per_ns.rs index 6e435c8c1..74665c588 100644 --- a/crates/ra_hir_def/src/per_ns.rs +++ b/crates/ra_hir_def/src/per_ns.rs | |||
@@ -5,7 +5,7 @@ | |||
5 | 5 | ||
6 | use hir_expand::MacroDefId; | 6 | use hir_expand::MacroDefId; |
7 | 7 | ||
8 | use crate::{visibility::Visibility, ModuleDefId}; | 8 | use crate::{item_scope::ItemInNs, visibility::Visibility, ModuleDefId}; |
9 | 9 | ||
10 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] | 10 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
11 | pub struct PerNs { | 11 | pub struct PerNs { |
@@ -84,4 +84,12 @@ impl PerNs { | |||
84 | macros: self.macros.or(other.macros), | 84 | macros: self.macros.or(other.macros), |
85 | } | 85 | } |
86 | } | 86 | } |
87 | |||
88 | pub fn iter_items(self) -> impl Iterator<Item = ItemInNs> { | ||
89 | self.types | ||
90 | .map(|it| ItemInNs::Types(it.0)) | ||
91 | .into_iter() | ||
92 | .chain(self.values.map(|it| ItemInNs::Values(it.0)).into_iter()) | ||
93 | .chain(self.macros.map(|it| ItemInNs::Macros(it.0)).into_iter()) | ||
94 | } | ||
87 | } | 95 | } |