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.rs679
1 files changed, 679 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..68e20d06b
--- /dev/null
+++ b/crates/ra_hir_def/src/import_map.rs
@@ -0,0 +1,679 @@
1//! A map of all publicly exported items in a crate.
2
3use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
4
5use fst::{self, Streamer};
6use indexmap::{map::Entry, IndexMap};
7use ra_db::CrateId;
8use rustc_hash::FxHasher;
9
10use crate::{
11 db::DefDatabase,
12 item_scope::ItemInNs,
13 path::{ModPath, PathKind},
14 visibility::Visibility,
15 ModuleDefId, ModuleId,
16};
17
18type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
19
20/// Item import details stored in the `ImportMap`.
21#[derive(Debug, Clone, Eq, PartialEq)]
22pub struct ImportInfo {
23 /// A path that can be used to import the item, relative to the crate's root.
24 pub path: ModPath,
25 /// The module containing this item.
26 pub container: ModuleId,
27}
28
29/// A map from publicly exported items to the path needed to import/name them from a downstream
30/// crate.
31///
32/// Reexports of items are taken into account, ie. if something is exported under multiple
33/// names, the one with the shortest import path will be used.
34///
35/// Note that all paths are relative to the containing crate's root, so the crate name still needs
36/// to be prepended to the `ModPath` before the path is valid.
37pub struct ImportMap {
38 map: FxIndexMap<ItemInNs, ImportInfo>,
39
40 /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
41 /// values returned by running `fst`.
42 ///
43 /// Since a path can refer to multiple items due to namespacing, we store all items with the
44 /// same path right after each other. This allows us to find all items after the FST gives us
45 /// the index of the first one.
46 importables: Vec<ItemInNs>,
47 fst: fst::Map<Vec<u8>>,
48}
49
50impl ImportMap {
51 pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
52 let _p = ra_prof::profile("import_map_query");
53 let def_map = db.crate_def_map(krate);
54 let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default());
55
56 // We look only into modules that are public(ly reexported), starting with the crate root.
57 let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
58 let root = ModuleId { krate, local_id: def_map.root };
59 let mut worklist = vec![(root, empty)];
60 while let Some((module, mod_path)) = worklist.pop() {
61 let ext_def_map;
62 let mod_data = if module.krate == krate {
63 &def_map[module.local_id]
64 } else {
65 // The crate might reexport a module defined in another crate.
66 ext_def_map = db.crate_def_map(module.krate);
67 &ext_def_map[module.local_id]
68 };
69
70 let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
71 let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
72 if per_ns.is_none() {
73 None
74 } else {
75 Some((name, per_ns))
76 }
77 });
78
79 for (name, per_ns) in visible_items {
80 let mk_path = || {
81 let mut path = mod_path.clone();
82 path.segments.push(name.clone());
83 path
84 };
85
86 for item in per_ns.iter_items() {
87 let path = mk_path();
88 match import_map.entry(item) {
89 Entry::Vacant(entry) => {
90 entry.insert(ImportInfo { path, container: module });
91 }
92 Entry::Occupied(mut entry) => {
93 // If the new path is shorter, prefer that one.
94 if path.len() < entry.get().path.len() {
95 *entry.get_mut() = ImportInfo { path, container: module };
96 } else {
97 continue;
98 }
99 }
100 }
101
102 // If we've just added a path to a module, descend into it. We might traverse
103 // modules multiple times, but only if the new path to it is shorter than the
104 // first (else we `continue` above).
105 if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
106 worklist.push((mod_id, mk_path()));
107 }
108 }
109 }
110 }
111
112 let mut importables = import_map.iter().collect::<Vec<_>>();
113
114 importables.sort_by(cmp);
115
116 // Build the FST, taking care not to insert duplicate values.
117
118 let mut builder = fst::MapBuilder::memory();
119 let mut last_batch_start = 0;
120
121 for idx in 0..importables.len() {
122 if let Some(next_item) = importables.get(idx + 1) {
123 if cmp(&importables[last_batch_start], next_item) == Ordering::Equal {
124 continue;
125 }
126 }
127
128 let start = last_batch_start;
129 last_batch_start = idx + 1;
130
131 let key = fst_path(&importables[start].1.path);
132
133 builder.insert(key, start as u64).unwrap();
134 }
135
136 let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
137 let importables = importables.iter().map(|(item, _)| **item).collect();
138
139 Arc::new(Self { map: import_map, fst, importables })
140 }
141
142 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
143 pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> {
144 Some(&self.map.get(&item)?.path)
145 }
146
147 pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
148 self.map.get(&item)
149 }
150}
151
152impl PartialEq for ImportMap {
153 fn eq(&self, other: &Self) -> bool {
154 // `fst` and `importables` are built from `map`, so we don't need to compare them.
155 self.map == other.map
156 }
157}
158
159impl Eq for ImportMap {}
160
161impl fmt::Debug for ImportMap {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 let mut importable_paths: Vec<_> = self
164 .map
165 .iter()
166 .map(|(item, info)| {
167 let ns = match item {
168 ItemInNs::Types(_) => "t",
169 ItemInNs::Values(_) => "v",
170 ItemInNs::Macros(_) => "m",
171 };
172 format!("- {} ({})", info.path, ns)
173 })
174 .collect();
175
176 importable_paths.sort();
177 f.write_str(&importable_paths.join("\n"))
178 }
179}
180
181fn fst_path(path: &ModPath) -> String {
182 let mut s = path.to_string();
183 s.make_ascii_lowercase();
184 s
185}
186
187fn cmp((_, lhs): &(&ItemInNs, &ImportInfo), (_, rhs): &(&ItemInNs, &ImportInfo)) -> Ordering {
188 let lhs_str = fst_path(&lhs.path);
189 let rhs_str = fst_path(&rhs.path);
190 lhs_str.cmp(&rhs_str)
191}
192
193#[derive(Debug)]
194pub struct Query {
195 query: String,
196 lowercased: String,
197 anchor_end: bool,
198 case_sensitive: bool,
199 limit: usize,
200}
201
202impl Query {
203 pub fn new(query: &str) -> Self {
204 Self {
205 lowercased: query.to_lowercase(),
206 query: query.to_string(),
207 anchor_end: false,
208 case_sensitive: false,
209 limit: usize::max_value(),
210 }
211 }
212
213 /// Only returns items whose paths end with the (case-insensitive) query string as their last
214 /// segment.
215 pub fn anchor_end(self) -> Self {
216 Self { anchor_end: true, ..self }
217 }
218
219 /// Limits the returned number of items to `limit`.
220 pub fn limit(self, limit: usize) -> Self {
221 Self { limit, ..self }
222 }
223
224 /// Respect casing of the query string when matching.
225 pub fn case_sensitive(self) -> Self {
226 Self { case_sensitive: true, ..self }
227 }
228}
229
230/// Searches dependencies of `krate` for an importable path matching `query`.
231///
232/// This returns a list of items that could be imported from dependencies of `krate`.
233pub fn search_dependencies<'a>(
234 db: &'a dyn DefDatabase,
235 krate: CrateId,
236 query: Query,
237) -> Vec<ItemInNs> {
238 let _p = ra_prof::profile("search_dependencies").detail(|| format!("{:?}", query));
239
240 let graph = db.crate_graph();
241 let import_maps: Vec<_> =
242 graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect();
243
244 let automaton = fst::automaton::Subsequence::new(&query.lowercased);
245
246 let mut op = fst::map::OpBuilder::new();
247 for map in &import_maps {
248 op = op.add(map.fst.search(&automaton));
249 }
250
251 let mut stream = op.union();
252 let mut res = Vec::new();
253 while let Some((_, indexed_values)) = stream.next() {
254 for indexed_value in indexed_values {
255 let import_map = &import_maps[indexed_value.index];
256 let importables = &import_map.importables[indexed_value.value as usize..];
257
258 // Path shared by the importable items in this group.
259 let path = &import_map.map[&importables[0]].path;
260
261 if query.anchor_end {
262 // Last segment must match query.
263 let last = path.segments.last().unwrap().to_string();
264 if last.to_lowercase() != query.lowercased {
265 continue;
266 }
267 }
268
269 // Add the items from this `ModPath` group. Those are all subsequent items in
270 // `importables` whose paths match `path`.
271 let iter = importables.iter().copied().take_while(|item| {
272 let item_path = &import_map.map[item].path;
273 fst_path(item_path) == fst_path(path)
274 });
275
276 if query.case_sensitive {
277 // FIXME: This does not do a subsequence match.
278 res.extend(iter.filter(|item| {
279 let item_path = &import_map.map[item].path;
280 item_path.to_string().contains(&query.query)
281 }));
282 } else {
283 res.extend(iter);
284 }
285
286 if res.len() >= query.limit {
287 res.truncate(query.limit);
288 return res;
289 }
290 }
291 }
292
293 res
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use crate::test_db::TestDB;
300 use insta::assert_snapshot;
301 use itertools::Itertools;
302 use ra_db::fixture::WithFixture;
303 use ra_db::{SourceDatabase, Upcast};
304
305 fn import_map(ra_fixture: &str) -> String {
306 let db = TestDB::with_files(ra_fixture);
307 let crate_graph = db.crate_graph();
308
309 let s = crate_graph
310 .iter()
311 .filter_map(|krate| {
312 let cdata = &crate_graph[krate];
313 let name = cdata.display_name.as_ref()?;
314
315 let map = db.import_map(krate);
316
317 Some(format!("{}:\n{:?}", name, map))
318 })
319 .join("\n");
320 s
321 }
322
323 fn search_dependencies_of(ra_fixture: &str, krate_name: &str, query: Query) -> String {
324 let db = TestDB::with_files(ra_fixture);
325 let crate_graph = db.crate_graph();
326 let krate = crate_graph
327 .iter()
328 .find(|krate| {
329 crate_graph[*krate].display_name.as_ref().map(|n| n.to_string())
330 == Some(krate_name.to_string())
331 })
332 .unwrap();
333
334 search_dependencies(db.upcast(), krate, query)
335 .into_iter()
336 .filter_map(|item| {
337 let mark = match item {
338 ItemInNs::Types(_) => "t",
339 ItemInNs::Values(_) => "v",
340 ItemInNs::Macros(_) => "m",
341 };
342 item.krate(db.upcast()).map(|krate| {
343 let map = db.import_map(krate);
344 let path = map.path_of(item).unwrap();
345 format!(
346 "{}::{} ({})",
347 crate_graph[krate].display_name.as_ref().unwrap(),
348 path,
349 mark
350 )
351 })
352 })
353 .join("\n")
354 }
355
356 #[test]
357 fn smoke() {
358 let map = import_map(
359 r"
360 //- /main.rs crate:main deps:lib
361
362 mod private {
363 pub use lib::Pub;
364 pub struct InPrivateModule;
365 }
366
367 pub mod publ1 {
368 use lib::Pub;
369 }
370
371 pub mod real_pub {
372 pub use lib::Pub;
373 }
374 pub mod real_pu2 { // same path length as above
375 pub use lib::Pub;
376 }
377
378 //- /lib.rs crate:lib
379 pub struct Pub {}
380 pub struct Pub2; // t + v
381 struct Priv;
382 ",
383 );
384
385 assert_snapshot!(map, @r###"
386 main:
387 - publ1 (t)
388 - real_pu2 (t)
389 - real_pub (t)
390 - real_pub::Pub (t)
391 lib:
392 - Pub (t)
393 - Pub2 (t)
394 - Pub2 (v)
395 "###);
396 }
397
398 #[test]
399 fn prefers_shortest_path() {
400 let map = import_map(
401 r"
402 //- /main.rs crate:main
403
404 pub mod sub {
405 pub mod subsub {
406 pub struct Def {}
407 }
408
409 pub use super::sub::subsub::Def;
410 }
411 ",
412 );
413
414 assert_snapshot!(map, @r###"
415 main:
416 - sub (t)
417 - sub::Def (t)
418 - sub::subsub (t)
419 "###);
420 }
421
422 #[test]
423 fn type_reexport_cross_crate() {
424 // Reexports need to be visible from a crate, even if the original crate exports the item
425 // at a shorter path.
426 let map = import_map(
427 r"
428 //- /main.rs crate:main deps:lib
429 pub mod m {
430 pub use lib::S;
431 }
432 //- /lib.rs crate:lib
433 pub struct S;
434 ",
435 );
436
437 assert_snapshot!(map, @r###"
438 main:
439 - m (t)
440 - m::S (t)
441 - m::S (v)
442 lib:
443 - S (t)
444 - S (v)
445 "###);
446 }
447
448 #[test]
449 fn macro_reexport() {
450 let map = import_map(
451 r"
452 //- /main.rs crate:main deps:lib
453 pub mod m {
454 pub use lib::pub_macro;
455 }
456 //- /lib.rs crate:lib
457 #[macro_export]
458 macro_rules! pub_macro {
459 () => {};
460 }
461 ",
462 );
463
464 assert_snapshot!(map, @r###"
465 main:
466 - m (t)
467 - m::pub_macro (m)
468 lib:
469 - pub_macro (m)
470 "###);
471 }
472
473 #[test]
474 fn module_reexport() {
475 // Reexporting modules from a dependency adds all contents to the import map.
476 let map = import_map(
477 r"
478 //- /main.rs crate:main deps:lib
479 pub use lib::module as reexported_module;
480 //- /lib.rs crate:lib
481 pub mod module {
482 pub struct S;
483 }
484 ",
485 );
486
487 assert_snapshot!(map, @r###"
488 main:
489 - reexported_module (t)
490 - reexported_module::S (t)
491 - reexported_module::S (v)
492 lib:
493 - module (t)
494 - module::S (t)
495 - module::S (v)
496 "###);
497 }
498
499 #[test]
500 fn cyclic_module_reexport() {
501 // A cyclic reexport does not hang.
502 let map = import_map(
503 r"
504 //- /lib.rs crate:lib
505 pub mod module {
506 pub struct S;
507 pub use super::sub::*;
508 }
509
510 pub mod sub {
511 pub use super::module;
512 }
513 ",
514 );
515
516 assert_snapshot!(map, @r###"
517 lib:
518 - module (t)
519 - module::S (t)
520 - module::S (v)
521 - sub (t)
522 "###);
523 }
524
525 #[test]
526 fn private_macro() {
527 let map = import_map(
528 r"
529 //- /lib.rs crate:lib
530 macro_rules! private_macro {
531 () => {};
532 }
533 ",
534 );
535
536 assert_snapshot!(map, @r###"
537 lib:
538 "###);
539 }
540
541 #[test]
542 fn namespacing() {
543 let map = import_map(
544 r"
545 //- /lib.rs crate:lib
546 pub struct Thing; // t + v
547 #[macro_export]
548 macro_rules! Thing { // m
549 () => {};
550 }
551 ",
552 );
553
554 assert_snapshot!(map, @r###"
555 lib:
556 - Thing (m)
557 - Thing (t)
558 - Thing (v)
559 "###);
560
561 let map = import_map(
562 r"
563 //- /lib.rs crate:lib
564 pub mod Thing {} // t
565 #[macro_export]
566 macro_rules! Thing { // m
567 () => {};
568 }
569 ",
570 );
571
572 assert_snapshot!(map, @r###"
573 lib:
574 - Thing (m)
575 - Thing (t)
576 "###);
577 }
578
579 #[test]
580 fn search() {
581 let ra_fixture = r#"
582 //- /main.rs crate:main deps:dep
583 //- /dep.rs crate:dep deps:tdep
584 use tdep::fmt as fmt_dep;
585 pub mod fmt {
586 pub trait Display {
587 fn fmt();
588 }
589 }
590 #[macro_export]
591 macro_rules! Fmt {
592 () => {};
593 }
594 pub struct Fmt;
595
596 pub fn format() {}
597 pub fn no() {}
598
599 //- /tdep.rs crate:tdep
600 pub mod fmt {
601 pub struct NotImportableFromMain;
602 }
603 "#;
604
605 let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt"));
606 assert_snapshot!(res, @r###"
607 dep::fmt (t)
608 dep::Fmt (t)
609 dep::Fmt (v)
610 dep::Fmt (m)
611 dep::fmt::Display (t)
612 dep::format (v)
613 "###);
614
615 let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end());
616 assert_snapshot!(res, @r###"
617 dep::fmt (t)
618 dep::Fmt (t)
619 dep::Fmt (v)
620 dep::Fmt (m)
621 "###);
622 }
623
624 #[test]
625 fn search_casing() {
626 let ra_fixture = r#"
627 //- /main.rs crate:main deps:dep
628 //- /dep.rs crate:dep
629
630 pub struct fmt;
631 pub struct FMT;
632 "#;
633
634 let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT"));
635
636 assert_snapshot!(res, @r###"
637 dep::fmt (t)
638 dep::fmt (v)
639 dep::FMT (t)
640 dep::FMT (v)
641 "###);
642
643 let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT").case_sensitive());
644
645 assert_snapshot!(res, @r###"
646 dep::FMT (t)
647 dep::FMT (v)
648 "###);
649 }
650
651 #[test]
652 fn search_limit() {
653 let res = search_dependencies_of(
654 r#"
655 //- /main.rs crate:main deps:dep
656 //- /dep.rs crate:dep
657 pub mod fmt {
658 pub trait Display {
659 fn fmt();
660 }
661 }
662 #[macro_export]
663 macro_rules! Fmt {
664 () => {};
665 }
666 pub struct Fmt;
667
668 pub fn format() {}
669 pub fn no() {}
670 "#,
671 "main",
672 Query::new("").limit(2),
673 );
674 assert_snapshot!(res, @r###"
675 dep::fmt (t)
676 dep::Fmt (t)
677 "###);
678 }
679}