diff options
author | Jonas Schievink <[email protected]> | 2020-06-09 16:32:42 +0100 |
---|---|---|
committer | Jonas Schievink <[email protected]> | 2020-06-10 11:38:58 +0100 |
commit | 4bcf8c8c68bd791f295aa06ef7903c006be3f356 (patch) | |
tree | 4bb9425b04484fcdedcc32f03728e60a438c0fbb | |
parent | 54936e8aa212ea5fdf737d8e1b0a02c231ed89eb (diff) |
Add an FST index to `ImportMap`
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | crates/ra_hir_def/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_hir_def/src/import_map.rs | 253 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/name.rs | 7 |
4 files changed, 261 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock index df79334c9..4c5551968 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -981,7 +981,9 @@ dependencies = [ | |||
981 | "anymap", | 981 | "anymap", |
982 | "drop_bomb", | 982 | "drop_bomb", |
983 | "either", | 983 | "either", |
984 | "fst", | ||
984 | "insta", | 985 | "insta", |
986 | "itertools", | ||
985 | "log", | 987 | "log", |
986 | "once_cell", | 988 | "once_cell", |
987 | "ra_arena", | 989 | "ra_arena", |
diff --git a/crates/ra_hir_def/Cargo.toml b/crates/ra_hir_def/Cargo.toml index b85358308..bd69abfc7 100644 --- a/crates/ra_hir_def/Cargo.toml +++ b/crates/ra_hir_def/Cargo.toml | |||
@@ -14,6 +14,8 @@ 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" | ||
17 | 19 | ||
18 | stdx = { path = "../stdx" } | 20 | stdx = { path = "../stdx" } |
19 | 21 | ||
diff --git a/crates/ra_hir_def/src/import_map.rs b/crates/ra_hir_def/src/import_map.rs index 4284a0a91..e9b2fe26e 100644 --- a/crates/ra_hir_def/src/import_map.rs +++ b/crates/ra_hir_def/src/import_map.rs | |||
@@ -1,7 +1,10 @@ | |||
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::cmp::Ordering; | ||
3 | use std::{collections::hash_map::Entry, fmt, sync::Arc}; | 4 | use std::{collections::hash_map::Entry, fmt, sync::Arc}; |
4 | 5 | ||
6 | use fst::{self, Streamer}; | ||
7 | use itertools::Itertools; | ||
5 | use ra_db::CrateId; | 8 | use ra_db::CrateId; |
6 | use rustc_hash::FxHashMap; | 9 | use rustc_hash::FxHashMap; |
7 | 10 | ||
@@ -21,9 +24,17 @@ use crate::{ | |||
21 | /// | 24 | /// |
22 | /// Note that all paths are relative to the containing crate's root, so the crate name still needs | 25 | /// 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. | 26 | /// to be prepended to the `ModPath` before the path is valid. |
24 | #[derive(Eq, PartialEq)] | ||
25 | pub struct ImportMap { | 27 | pub struct ImportMap { |
26 | map: FxHashMap<ItemInNs, ModPath>, | 28 | map: FxHashMap<ItemInNs, ModPath>, |
29 | |||
30 | /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the | ||
31 | /// values returned by running `fst`. | ||
32 | /// | ||
33 | /// Since a path can refer to multiple items due to namespacing, we store all items with the | ||
34 | /// same path right after each other. This allows us to find all items after the FST gives us | ||
35 | /// the index of the first one. | ||
36 | importables: Vec<ItemInNs>, | ||
37 | fst: fst::Map<Vec<u8>>, | ||
27 | } | 38 | } |
28 | 39 | ||
29 | impl ImportMap { | 40 | impl ImportMap { |
@@ -88,7 +99,34 @@ impl ImportMap { | |||
88 | } | 99 | } |
89 | } | 100 | } |
90 | 101 | ||
91 | Arc::new(Self { map: import_map }) | 102 | let mut importables = import_map.iter().collect::<Vec<_>>(); |
103 | |||
104 | importables.sort_by(cmp); | ||
105 | |||
106 | // Build the FST, taking care not to insert duplicate values. | ||
107 | |||
108 | let mut builder = fst::MapBuilder::memory(); | ||
109 | let mut last_batch_start = 0; | ||
110 | |||
111 | for idx in 0..importables.len() { | ||
112 | if let Some(next_item) = importables.get(idx + 1) { | ||
113 | if cmp(&importables[last_batch_start], next_item) == Ordering::Equal { | ||
114 | continue; | ||
115 | } | ||
116 | } | ||
117 | |||
118 | let start = last_batch_start; | ||
119 | last_batch_start = idx + 1; | ||
120 | |||
121 | let key: String = fst_path(&importables[start].1).collect(); | ||
122 | |||
123 | builder.insert(key, start as u64).unwrap(); | ||
124 | } | ||
125 | |||
126 | let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap(); | ||
127 | let importables = importables.iter().map(|(item, _)| **item).collect(); | ||
128 | |||
129 | Arc::new(Self { map: import_map, fst, importables }) | ||
92 | } | 130 | } |
93 | 131 | ||
94 | /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. | 132 | /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. |
@@ -97,6 +135,14 @@ impl ImportMap { | |||
97 | } | 135 | } |
98 | } | 136 | } |
99 | 137 | ||
138 | impl PartialEq for ImportMap { | ||
139 | fn eq(&self, other: &Self) -> bool { | ||
140 | self.importables == other.importables | ||
141 | } | ||
142 | } | ||
143 | |||
144 | impl Eq for ImportMap {} | ||
145 | |||
100 | impl fmt::Debug for ImportMap { | 146 | impl fmt::Debug for ImportMap { |
101 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 147 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
102 | let mut importable_paths: Vec<_> = self | 148 | let mut importable_paths: Vec<_> = self |
@@ -117,13 +163,97 @@ impl fmt::Debug for ImportMap { | |||
117 | } | 163 | } |
118 | } | 164 | } |
119 | 165 | ||
166 | fn fst_path(path: &ModPath) -> impl Iterator<Item = char> + '_ { | ||
167 | path.segments | ||
168 | .iter() | ||
169 | .map(|name| name.as_text().unwrap()) | ||
170 | .intersperse("::") | ||
171 | .flat_map(|s| s.chars().map(|c| c.to_ascii_lowercase())) | ||
172 | } | ||
173 | |||
174 | fn cmp((_, lhs): &(&ItemInNs, &ModPath), (_, rhs): &(&ItemInNs, &ModPath)) -> Ordering { | ||
175 | let lhs_chars = fst_path(lhs); | ||
176 | let rhs_chars = fst_path(rhs); | ||
177 | lhs_chars.cmp(rhs_chars) | ||
178 | } | ||
179 | |||
180 | #[derive(Debug)] | ||
181 | pub struct Query { | ||
182 | query: String, | ||
183 | anchor_end: bool, | ||
184 | } | ||
185 | |||
186 | impl Query { | ||
187 | pub fn new(query: impl AsRef<str>) -> Self { | ||
188 | Self { query: query.as_ref().to_lowercase(), anchor_end: false } | ||
189 | } | ||
190 | |||
191 | /// Only returns items whose paths end with the (case-insensitive) query string as their last | ||
192 | /// segment. | ||
193 | pub fn anchor_end(self) -> Self { | ||
194 | Self { anchor_end: true, ..self } | ||
195 | } | ||
196 | } | ||
197 | |||
198 | /// Searches dependencies of `krate` for an importable path matching `query`. | ||
199 | /// | ||
200 | /// This returns all items that could be imported from within `krate`, excluding paths inside | ||
201 | /// `krate` itself. | ||
202 | pub fn search_dependencies<'a>( | ||
203 | db: &'a dyn DefDatabase, | ||
204 | krate: CrateId, | ||
205 | query: Query, | ||
206 | ) -> Vec<ItemInNs> { | ||
207 | let _p = ra_prof::profile("import_map::global_search").detail(|| format!("{:?}", query)); | ||
208 | |||
209 | let graph = db.crate_graph(); | ||
210 | let import_maps: Vec<_> = | ||
211 | graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect(); | ||
212 | |||
213 | let automaton = fst::automaton::Subsequence::new(&query.query); | ||
214 | |||
215 | let mut op = fst::map::OpBuilder::new(); | ||
216 | for map in &import_maps { | ||
217 | op = op.add(map.fst.search(&automaton)); | ||
218 | } | ||
219 | |||
220 | let mut stream = op.union(); | ||
221 | let mut res = Vec::new(); | ||
222 | while let Some((_, indexed_values)) = stream.next() { | ||
223 | for indexed_value in indexed_values { | ||
224 | let import_map = &import_maps[indexed_value.index]; | ||
225 | let importables = &import_map.importables[indexed_value.value as usize..]; | ||
226 | |||
227 | // Path shared by the importable items in this group. | ||
228 | let path = &import_map.map[&importables[0]]; | ||
229 | |||
230 | if query.anchor_end { | ||
231 | // Last segment must match query. | ||
232 | let last = path.segments.last().unwrap().to_string(); | ||
233 | if last.to_lowercase() != query.query { | ||
234 | continue; | ||
235 | } | ||
236 | } | ||
237 | |||
238 | // Add the items from this `ModPath` group. Those are all subsequent items in | ||
239 | // `importables` whose paths match `path`. | ||
240 | res.extend(importables.iter().copied().take_while(|item| { | ||
241 | let item_path = &import_map.map[item]; | ||
242 | fst_path(item_path).eq(fst_path(path)) | ||
243 | })); | ||
244 | } | ||
245 | } | ||
246 | |||
247 | res | ||
248 | } | ||
249 | |||
120 | #[cfg(test)] | 250 | #[cfg(test)] |
121 | mod tests { | 251 | mod tests { |
122 | use super::*; | 252 | use super::*; |
123 | use crate::test_db::TestDB; | 253 | use crate::test_db::TestDB; |
124 | use insta::assert_snapshot; | 254 | use insta::assert_snapshot; |
125 | use ra_db::fixture::WithFixture; | 255 | use ra_db::fixture::WithFixture; |
126 | use ra_db::SourceDatabase; | 256 | use ra_db::{SourceDatabase, Upcast}; |
127 | 257 | ||
128 | fn import_map(ra_fixture: &str) -> String { | 258 | fn import_map(ra_fixture: &str) -> String { |
129 | let db = TestDB::with_files(ra_fixture); | 259 | let db = TestDB::with_files(ra_fixture); |
@@ -144,6 +274,40 @@ mod tests { | |||
144 | import_maps.join("\n") | 274 | import_maps.join("\n") |
145 | } | 275 | } |
146 | 276 | ||
277 | fn search_dependencies_of(ra_fixture: &str, krate_name: &str, query: Query) -> String { | ||
278 | let db = TestDB::with_files(ra_fixture); | ||
279 | let crate_graph = db.crate_graph(); | ||
280 | let krate = crate_graph | ||
281 | .iter() | ||
282 | .find(|krate| { | ||
283 | crate_graph[*krate].display_name.as_ref().map(|n| n.to_string()) | ||
284 | == Some(krate_name.to_string()) | ||
285 | }) | ||
286 | .unwrap(); | ||
287 | |||
288 | search_dependencies(db.upcast(), krate, query) | ||
289 | .into_iter() | ||
290 | .filter_map(|item| { | ||
291 | let mark = match item { | ||
292 | ItemInNs::Types(_) => "t", | ||
293 | ItemInNs::Values(_) => "v", | ||
294 | ItemInNs::Macros(_) => "m", | ||
295 | }; | ||
296 | item.krate(db.upcast()).map(|krate| { | ||
297 | let map = db.import_map(krate); | ||
298 | let path = map.path_of(item).unwrap(); | ||
299 | format!( | ||
300 | "{}::{} ({})", | ||
301 | crate_graph[krate].display_name.as_ref().unwrap(), | ||
302 | path, | ||
303 | mark | ||
304 | ) | ||
305 | }) | ||
306 | }) | ||
307 | .collect::<Vec<_>>() | ||
308 | .join("\n") | ||
309 | } | ||
310 | |||
147 | #[test] | 311 | #[test] |
148 | fn smoke() { | 312 | fn smoke() { |
149 | let map = import_map( | 313 | let map = import_map( |
@@ -328,4 +492,87 @@ mod tests { | |||
328 | lib: | 492 | lib: |
329 | "###); | 493 | "###); |
330 | } | 494 | } |
495 | |||
496 | #[test] | ||
497 | fn namespacing() { | ||
498 | let map = import_map( | ||
499 | r" | ||
500 | //- /lib.rs crate:lib | ||
501 | pub struct Thing; // t + v | ||
502 | #[macro_export] | ||
503 | macro_rules! Thing { // m | ||
504 | () => {}; | ||
505 | } | ||
506 | ", | ||
507 | ); | ||
508 | |||
509 | assert_snapshot!(map, @r###" | ||
510 | lib: | ||
511 | - Thing (m) | ||
512 | - Thing (t) | ||
513 | - Thing (v) | ||
514 | "###); | ||
515 | |||
516 | let map = import_map( | ||
517 | r" | ||
518 | //- /lib.rs crate:lib | ||
519 | pub mod Thing {} // t | ||
520 | #[macro_export] | ||
521 | macro_rules! Thing { // m | ||
522 | () => {}; | ||
523 | } | ||
524 | ", | ||
525 | ); | ||
526 | |||
527 | assert_snapshot!(map, @r###" | ||
528 | lib: | ||
529 | - Thing (m) | ||
530 | - Thing (t) | ||
531 | "###); | ||
532 | } | ||
533 | |||
534 | #[test] | ||
535 | fn search() { | ||
536 | let ra_fixture = r#" | ||
537 | //- /main.rs crate:main deps:dep | ||
538 | //- /dep.rs crate:dep deps:tdep | ||
539 | use tdep::fmt as fmt_dep; | ||
540 | pub mod fmt { | ||
541 | pub trait Display { | ||
542 | fn fmt(); | ||
543 | } | ||
544 | } | ||
545 | #[macro_export] | ||
546 | macro_rules! Fmt { | ||
547 | () => {}; | ||
548 | } | ||
549 | pub struct Fmt; | ||
550 | |||
551 | pub fn format() {} | ||
552 | pub fn no() {} | ||
553 | |||
554 | //- /tdep.rs crate:tdep | ||
555 | pub mod fmt { | ||
556 | pub struct NotImportableFromMain; | ||
557 | } | ||
558 | "#; | ||
559 | |||
560 | let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt")); | ||
561 | assert_snapshot!(res, @r###" | ||
562 | dep::Fmt (v) | ||
563 | dep::fmt (t) | ||
564 | dep::Fmt (t) | ||
565 | dep::Fmt (m) | ||
566 | dep::fmt::Display (t) | ||
567 | dep::format (v) | ||
568 | "###); | ||
569 | |||
570 | let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end()); | ||
571 | assert_snapshot!(res, @r###" | ||
572 | dep::Fmt (v) | ||
573 | dep::fmt (t) | ||
574 | dep::Fmt (t) | ||
575 | dep::Fmt (m) | ||
576 | "###); | ||
577 | } | ||
331 | } | 578 | } |
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index 660bdfe33..f306d9641 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs | |||
@@ -67,6 +67,13 @@ impl Name { | |||
67 | _ => None, | 67 | _ => None, |
68 | } | 68 | } |
69 | } | 69 | } |
70 | |||
71 | pub fn as_text(&self) -> Option<&str> { | ||
72 | match &self.0 { | ||
73 | Repr::Text(s) => Some(s.as_str()), | ||
74 | _ => None, | ||
75 | } | ||
76 | } | ||
70 | } | 77 | } |
71 | 78 | ||
72 | pub trait AsName { | 79 | pub trait AsName { |