aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_def
diff options
context:
space:
mode:
authorJonas Schievink <[email protected]>2020-05-20 22:51:20 +0100
committerJonas Schievink <[email protected]>2020-06-04 18:33:01 +0100
commitd08c63cb9e3574fa97374a8529136814530bf416 (patch)
tree385c05e8531fccb567ca7ca688de761f4a08edb3 /crates/ra_hir_def
parentc19496f845a4adcd7e0f48f5dcb5b405bbc63dfc (diff)
Add an ImportMap
Diffstat (limited to 'crates/ra_hir_def')
-rw-r--r--crates/ra_hir_def/src/db.rs4
-rw-r--r--crates/ra_hir_def/src/find_path.rs19
-rw-r--r--crates/ra_hir_def/src/import_map.rs323
-rw-r--r--crates/ra_hir_def/src/lib.rs1
-rw-r--r--crates/ra_hir_def/src/path.rs13
-rw-r--r--crates/ra_hir_def/src/per_ns.rs10
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
127fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { 131fn 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
52pub(crate) fn find_path_inner_query( 41pub(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
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}
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
44pub mod visibility; 44pub mod visibility;
45pub mod find_path; 45pub mod find_path;
46pub mod import_map;
46 47
47#[cfg(test)] 48#[cfg(test)]
48mod test_db; 49mod 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
6use hir_expand::MacroDefId; 6use hir_expand::MacroDefId;
7 7
8use crate::{visibility::Visibility, ModuleDefId}; 8use crate::{item_scope::ItemInNs, visibility::Visibility, ModuleDefId};
9 9
10#[derive(Debug, Copy, Clone, PartialEq, Eq)] 10#[derive(Debug, Copy, Clone, PartialEq, Eq)]
11pub struct PerNs { 11pub 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}