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.rs355
1 files changed, 345 insertions, 10 deletions
diff --git a/crates/ra_hir_def/src/import_map.rs b/crates/ra_hir_def/src/import_map.rs
index 4284a0a91..36b4fdd81 100644
--- a/crates/ra_hir_def/src/import_map.rs
+++ b/crates/ra_hir_def/src/import_map.rs
@@ -1,9 +1,11 @@
1//! A map of all publicly exported items in a crate. 1//! A map of all publicly exported items in a crate.
2 2
3use std::{collections::hash_map::Entry, fmt, sync::Arc}; 3use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
4 4
5use fst::{self, Streamer};
6use indexmap::{map::Entry, IndexMap};
5use ra_db::CrateId; 7use ra_db::CrateId;
6use rustc_hash::FxHashMap; 8use rustc_hash::FxHasher;
7 9
8use crate::{ 10use crate::{
9 db::DefDatabase, 11 db::DefDatabase,
@@ -13,6 +15,8 @@ use crate::{
13 ModuleDefId, ModuleId, 15 ModuleDefId, ModuleId,
14}; 16};
15 17
18type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
19
16/// A map from publicly exported items to the path needed to import/name them from a downstream 20/// A map from publicly exported items to the path needed to import/name them from a downstream
17/// crate. 21/// crate.
18/// 22///
@@ -21,16 +25,24 @@ use crate::{
21/// 25///
22/// Note that all paths are relative to the containing crate's root, so the crate name still needs 26/// 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. 27/// to be prepended to the `ModPath` before the path is valid.
24#[derive(Eq, PartialEq)]
25pub struct ImportMap { 28pub struct ImportMap {
26 map: FxHashMap<ItemInNs, ModPath>, 29 map: FxIndexMap<ItemInNs, ModPath>,
30
31 /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
32 /// values returned by running `fst`.
33 ///
34 /// Since a path can refer to multiple items due to namespacing, we store all items with the
35 /// same path right after each other. This allows us to find all items after the FST gives us
36 /// the index of the first one.
37 importables: Vec<ItemInNs>,
38 fst: fst::Map<Vec<u8>>,
27} 39}
28 40
29impl ImportMap { 41impl ImportMap {
30 pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> { 42 pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
31 let _p = ra_prof::profile("import_map_query"); 43 let _p = ra_prof::profile("import_map_query");
32 let def_map = db.crate_def_map(krate); 44 let def_map = db.crate_def_map(krate);
33 let mut import_map = FxHashMap::with_capacity_and_hasher(64, Default::default()); 45 let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default());
34 46
35 // We look only into modules that are public(ly reexported), starting with the crate root. 47 // We look only into modules that are public(ly reexported), starting with the crate root.
36 let empty = ModPath { kind: PathKind::Plain, segments: vec![] }; 48 let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
@@ -88,7 +100,34 @@ impl ImportMap {
88 } 100 }
89 } 101 }
90 102
91 Arc::new(Self { map: import_map }) 103 let mut importables = import_map.iter().collect::<Vec<_>>();
104
105 importables.sort_by(cmp);
106
107 // Build the FST, taking care not to insert duplicate values.
108
109 let mut builder = fst::MapBuilder::memory();
110 let mut last_batch_start = 0;
111
112 for idx in 0..importables.len() {
113 if let Some(next_item) = importables.get(idx + 1) {
114 if cmp(&importables[last_batch_start], next_item) == Ordering::Equal {
115 continue;
116 }
117 }
118
119 let start = last_batch_start;
120 last_batch_start = idx + 1;
121
122 let key = fst_path(&importables[start].1);
123
124 builder.insert(key, start as u64).unwrap();
125 }
126
127 let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
128 let importables = importables.iter().map(|(item, _)| **item).collect();
129
130 Arc::new(Self { map: import_map, fst, importables })
92 } 131 }
93 132
94 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. 133 /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
@@ -97,6 +136,15 @@ impl ImportMap {
97 } 136 }
98} 137}
99 138
139impl PartialEq for ImportMap {
140 fn eq(&self, other: &Self) -> bool {
141 // `fst` and `importables` are built from `map`, so we don't need to compare them.
142 self.map == other.map
143 }
144}
145
146impl Eq for ImportMap {}
147
100impl fmt::Debug for ImportMap { 148impl fmt::Debug for ImportMap {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 let mut importable_paths: Vec<_> = self 150 let mut importable_paths: Vec<_> = self
@@ -117,19 +165,135 @@ impl fmt::Debug for ImportMap {
117 } 165 }
118} 166}
119 167
168fn fst_path(path: &ModPath) -> String {
169 let mut s = path.to_string();
170 s.make_ascii_lowercase();
171 s
172}
173
174fn cmp((_, lhs): &(&ItemInNs, &ModPath), (_, rhs): &(&ItemInNs, &ModPath)) -> Ordering {
175 let lhs_str = fst_path(lhs);
176 let rhs_str = fst_path(rhs);
177 lhs_str.cmp(&rhs_str)
178}
179
180#[derive(Debug)]
181pub struct Query {
182 query: String,
183 lowercased: String,
184 anchor_end: bool,
185 case_sensitive: bool,
186 limit: usize,
187}
188
189impl Query {
190 pub fn new(query: &str) -> Self {
191 Self {
192 lowercased: query.to_lowercase(),
193 query: query.to_string(),
194 anchor_end: false,
195 case_sensitive: false,
196 limit: usize::max_value(),
197 }
198 }
199
200 /// Only returns items whose paths end with the (case-insensitive) query string as their last
201 /// segment.
202 pub fn anchor_end(self) -> Self {
203 Self { anchor_end: true, ..self }
204 }
205
206 /// Limits the returned number of items to `limit`.
207 pub fn limit(self, limit: usize) -> Self {
208 Self { limit, ..self }
209 }
210
211 /// Respect casing of the query string when matching.
212 pub fn case_sensitive(self) -> Self {
213 Self { case_sensitive: true, ..self }
214 }
215}
216
217/// Searches dependencies of `krate` for an importable path matching `query`.
218///
219/// This returns a list of items that could be imported from dependencies of `krate`.
220pub fn search_dependencies<'a>(
221 db: &'a dyn DefDatabase,
222 krate: CrateId,
223 query: Query,
224) -> Vec<ItemInNs> {
225 let _p = ra_prof::profile("search_dependencies").detail(|| format!("{:?}", query));
226
227 let graph = db.crate_graph();
228 let import_maps: Vec<_> =
229 graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect();
230
231 let automaton = fst::automaton::Subsequence::new(&query.lowercased);
232
233 let mut op = fst::map::OpBuilder::new();
234 for map in &import_maps {
235 op = op.add(map.fst.search(&automaton));
236 }
237
238 let mut stream = op.union();
239 let mut res = Vec::new();
240 while let Some((_, indexed_values)) = stream.next() {
241 for indexed_value in indexed_values {
242 let import_map = &import_maps[indexed_value.index];
243 let importables = &import_map.importables[indexed_value.value as usize..];
244
245 // Path shared by the importable items in this group.
246 let path = &import_map.map[&importables[0]];
247
248 if query.anchor_end {
249 // Last segment must match query.
250 let last = path.segments.last().unwrap().to_string();
251 if last.to_lowercase() != query.lowercased {
252 continue;
253 }
254 }
255
256 // Add the items from this `ModPath` group. Those are all subsequent items in
257 // `importables` whose paths match `path`.
258 let iter = importables.iter().copied().take_while(|item| {
259 let item_path = &import_map.map[item];
260 fst_path(item_path) == fst_path(path)
261 });
262
263 if query.case_sensitive {
264 // FIXME: This does not do a subsequence match.
265 res.extend(iter.filter(|item| {
266 let item_path = &import_map.map[item];
267 item_path.to_string().contains(&query.query)
268 }));
269 } else {
270 res.extend(iter);
271 }
272
273 if res.len() >= query.limit {
274 res.truncate(query.limit);
275 return res;
276 }
277 }
278 }
279
280 res
281}
282
120#[cfg(test)] 283#[cfg(test)]
121mod tests { 284mod tests {
122 use super::*; 285 use super::*;
123 use crate::test_db::TestDB; 286 use crate::test_db::TestDB;
124 use insta::assert_snapshot; 287 use insta::assert_snapshot;
288 use itertools::Itertools;
125 use ra_db::fixture::WithFixture; 289 use ra_db::fixture::WithFixture;
126 use ra_db::SourceDatabase; 290 use ra_db::{SourceDatabase, Upcast};
127 291
128 fn import_map(ra_fixture: &str) -> String { 292 fn import_map(ra_fixture: &str) -> String {
129 let db = TestDB::with_files(ra_fixture); 293 let db = TestDB::with_files(ra_fixture);
130 let crate_graph = db.crate_graph(); 294 let crate_graph = db.crate_graph();
131 295
132 let import_maps: Vec<_> = crate_graph 296 let s = crate_graph
133 .iter() 297 .iter()
134 .filter_map(|krate| { 298 .filter_map(|krate| {
135 let cdata = &crate_graph[krate]; 299 let cdata = &crate_graph[krate];
@@ -139,9 +303,41 @@ mod tests {
139 303
140 Some(format!("{}:\n{:?}", name, map)) 304 Some(format!("{}:\n{:?}", name, map))
141 }) 305 })
142 .collect(); 306 .join("\n");
307 s
308 }
143 309
144 import_maps.join("\n") 310 fn search_dependencies_of(ra_fixture: &str, krate_name: &str, query: Query) -> String {
311 let db = TestDB::with_files(ra_fixture);
312 let crate_graph = db.crate_graph();
313 let krate = crate_graph
314 .iter()
315 .find(|krate| {
316 crate_graph[*krate].display_name.as_ref().map(|n| n.to_string())
317 == Some(krate_name.to_string())
318 })
319 .unwrap();
320
321 search_dependencies(db.upcast(), krate, query)
322 .into_iter()
323 .filter_map(|item| {
324 let mark = match item {
325 ItemInNs::Types(_) => "t",
326 ItemInNs::Values(_) => "v",
327 ItemInNs::Macros(_) => "m",
328 };
329 item.krate(db.upcast()).map(|krate| {
330 let map = db.import_map(krate);
331 let path = map.path_of(item).unwrap();
332 format!(
333 "{}::{} ({})",
334 crate_graph[krate].display_name.as_ref().unwrap(),
335 path,
336 mark
337 )
338 })
339 })
340 .join("\n")
145 } 341 }
146 342
147 #[test] 343 #[test]
@@ -328,4 +524,143 @@ mod tests {
328 lib: 524 lib:
329 "###); 525 "###);
330 } 526 }
527
528 #[test]
529 fn namespacing() {
530 let map = import_map(
531 r"
532 //- /lib.rs crate:lib
533 pub struct Thing; // t + v
534 #[macro_export]
535 macro_rules! Thing { // m
536 () => {};
537 }
538 ",
539 );
540
541 assert_snapshot!(map, @r###"
542 lib:
543 - Thing (m)
544 - Thing (t)
545 - Thing (v)
546 "###);
547
548 let map = import_map(
549 r"
550 //- /lib.rs crate:lib
551 pub mod Thing {} // t
552 #[macro_export]
553 macro_rules! Thing { // m
554 () => {};
555 }
556 ",
557 );
558
559 assert_snapshot!(map, @r###"
560 lib:
561 - Thing (m)
562 - Thing (t)
563 "###);
564 }
565
566 #[test]
567 fn search() {
568 let ra_fixture = r#"
569 //- /main.rs crate:main deps:dep
570 //- /dep.rs crate:dep deps:tdep
571 use tdep::fmt as fmt_dep;
572 pub mod fmt {
573 pub trait Display {
574 fn fmt();
575 }
576 }
577 #[macro_export]
578 macro_rules! Fmt {
579 () => {};
580 }
581 pub struct Fmt;
582
583 pub fn format() {}
584 pub fn no() {}
585
586 //- /tdep.rs crate:tdep
587 pub mod fmt {
588 pub struct NotImportableFromMain;
589 }
590 "#;
591
592 let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt"));
593 assert_snapshot!(res, @r###"
594 dep::fmt (t)
595 dep::Fmt (t)
596 dep::Fmt (v)
597 dep::Fmt (m)
598 dep::fmt::Display (t)
599 dep::format (v)
600 "###);
601
602 let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end());
603 assert_snapshot!(res, @r###"
604 dep::fmt (t)
605 dep::Fmt (t)
606 dep::Fmt (v)
607 dep::Fmt (m)
608 "###);
609 }
610
611 #[test]
612 fn search_casing() {
613 let ra_fixture = r#"
614 //- /main.rs crate:main deps:dep
615 //- /dep.rs crate:dep
616
617 pub struct fmt;
618 pub struct FMT;
619 "#;
620
621 let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT"));
622
623 assert_snapshot!(res, @r###"
624 dep::fmt (t)
625 dep::fmt (v)
626 dep::FMT (t)
627 dep::FMT (v)
628 "###);
629
630 let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT").case_sensitive());
631
632 assert_snapshot!(res, @r###"
633 dep::FMT (t)
634 dep::FMT (v)
635 "###);
636 }
637
638 #[test]
639 fn search_limit() {
640 let res = search_dependencies_of(
641 r#"
642 //- /main.rs crate:main deps:dep
643 //- /dep.rs crate:dep
644 pub mod fmt {
645 pub trait Display {
646 fn fmt();
647 }
648 }
649 #[macro_export]
650 macro_rules! Fmt {
651 () => {};
652 }
653 pub struct Fmt;
654
655 pub fn format() {}
656 pub fn no() {}
657 "#,
658 "main",
659 Query::new("").limit(2),
660 );
661 assert_snapshot!(res, @r###"
662 dep::fmt (t)
663 dep::Fmt (t)
664 "###);
665 }
331} 666}