aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Schievink <[email protected]>2020-06-09 16:32:42 +0100
committerJonas Schievink <[email protected]>2020-06-10 11:38:58 +0100
commit4bcf8c8c68bd791f295aa06ef7903c006be3f356 (patch)
tree4bb9425b04484fcdedcc32f03728e60a438c0fbb
parent54936e8aa212ea5fdf737d8e1b0a02c231ed89eb (diff)
Add an FST index to `ImportMap`
-rw-r--r--Cargo.lock2
-rw-r--r--crates/ra_hir_def/Cargo.toml2
-rw-r--r--crates/ra_hir_def/src/import_map.rs253
-rw-r--r--crates/ra_hir_expand/src/name.rs7
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"
14either = "1.5.3" 14either = "1.5.3"
15anymap = "0.12.1" 15anymap = "0.12.1"
16drop_bomb = "0.1.4" 16drop_bomb = "0.1.4"
17fst = { version = "0.4", default-features = false }
18itertools = "0.9.0"
17 19
18stdx = { path = "../stdx" } 20stdx = { 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
3use std::cmp::Ordering;
3use std::{collections::hash_map::Entry, fmt, sync::Arc}; 4use std::{collections::hash_map::Entry, fmt, sync::Arc};
4 5
6use fst::{self, Streamer};
7use itertools::Itertools;
5use ra_db::CrateId; 8use ra_db::CrateId;
6use rustc_hash::FxHashMap; 9use 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)]
25pub struct ImportMap { 27pub 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
29impl ImportMap { 40impl 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
138impl PartialEq for ImportMap {
139 fn eq(&self, other: &Self) -> bool {
140 self.importables == other.importables
141 }
142}
143
144impl Eq for ImportMap {}
145
100impl fmt::Debug for ImportMap { 146impl 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
166fn 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
174fn 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)]
181pub struct Query {
182 query: String,
183 anchor_end: bool,
184}
185
186impl 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.
202pub 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)]
121mod tests { 251mod 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
72pub trait AsName { 79pub trait AsName {