diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-06-11 11:07:52 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-06-11 11:07:52 +0100 |
commit | f320c38aec185de2a02e0febff133da1f6a3462b (patch) | |
tree | f22c1d5af6dcd994667d656833c3a27c568961ff /crates/ra_hir_def | |
parent | dfbd81e84a699005db4e88e9fbf8d4440525a097 (diff) | |
parent | 6766a6b0e189f47d7a405c872598bca9a2395360 (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')
-rw-r--r-- | crates/ra_hir_def/Cargo.toml | 3 | ||||
-rw-r--r-- | crates/ra_hir_def/src/import_map.rs | 355 |
2 files changed, 348 insertions, 10 deletions
diff --git a/crates/ra_hir_def/Cargo.toml b/crates/ra_hir_def/Cargo.toml index b85358308..ef1f65ee0 100644 --- a/crates/ra_hir_def/Cargo.toml +++ b/crates/ra_hir_def/Cargo.toml | |||
@@ -14,6 +14,9 @@ rustc-hash = "1.1.0" | |||
14 | either = "1.5.3" | 14 | either = "1.5.3" |
15 | anymap = "0.12.1" | 15 | anymap = "0.12.1" |
16 | drop_bomb = "0.1.4" | 16 | drop_bomb = "0.1.4" |
17 | fst = { version = "0.4", default-features = false } | ||
18 | itertools = "0.9.0" | ||
19 | indexmap = "1.4.0" | ||
17 | 20 | ||
18 | stdx = { path = "../stdx" } | 21 | stdx = { path = "../stdx" } |
19 | 22 | ||
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 | ||
3 | use std::{collections::hash_map::Entry, fmt, sync::Arc}; | 3 | use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc}; |
4 | 4 | ||
5 | use fst::{self, Streamer}; | ||
6 | use indexmap::{map::Entry, IndexMap}; | ||
5 | use ra_db::CrateId; | 7 | use ra_db::CrateId; |
6 | use rustc_hash::FxHashMap; | 8 | use rustc_hash::FxHasher; |
7 | 9 | ||
8 | use crate::{ | 10 | use crate::{ |
9 | db::DefDatabase, | 11 | db::DefDatabase, |
@@ -13,6 +15,8 @@ use crate::{ | |||
13 | ModuleDefId, ModuleId, | 15 | ModuleDefId, ModuleId, |
14 | }; | 16 | }; |
15 | 17 | ||
18 | type 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)] | ||
25 | pub struct ImportMap { | 28 | pub 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 | ||
29 | impl ImportMap { | 41 | impl 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 | ||
139 | impl 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 | |||
146 | impl Eq for ImportMap {} | ||
147 | |||
100 | impl fmt::Debug for ImportMap { | 148 | impl 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 | ||
168 | fn fst_path(path: &ModPath) -> String { | ||
169 | let mut s = path.to_string(); | ||
170 | s.make_ascii_lowercase(); | ||
171 | s | ||
172 | } | ||
173 | |||
174 | fn 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)] | ||
181 | pub struct Query { | ||
182 | query: String, | ||
183 | lowercased: String, | ||
184 | anchor_end: bool, | ||
185 | case_sensitive: bool, | ||
186 | limit: usize, | ||
187 | } | ||
188 | |||
189 | impl 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`. | ||
220 | pub 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)] |
121 | mod tests { | 284 | mod 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 | } |