diff options
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 | } |