aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_def/src/import_map.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-06-11 11:07:52 +0100
committerGitHub <[email protected]>2020-06-11 11:07:52 +0100
commitf320c38aec185de2a02e0febff133da1f6a3462b (patch)
treef22c1d5af6dcd994667d656833c3a27c568961ff /crates/ra_hir_def/src/import_map.rs
parentdfbd81e84a699005db4e88e9fbf8d4440525a097 (diff)
parent6766a6b0e189f47d7a405c872598bca9a2395360 (diff)
Merge #4819
4819: Add an FST index to `ImportMap` and use it to speed up auto import r=matklad a=jonas-schievink For the importing crate, we still use the symbol index, but I've modified it to only look at files that comprise that crate (instead of the whole workspace). Oh, and since now the symbol query limit is respected correctly, it's possible that some results from the local crate now disappear if there are many matches. Fixes https://github.com/rust-analyzer/rust-analyzer/issues/4763 Co-authored-by: Jonas Schievink <[email protected]>
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}