1
1
//! A map of all publicly exported items in a crate.
2
2
3
+ use std:: cmp:: Ordering ;
3
4
use std:: { collections:: hash_map:: Entry , fmt, sync:: Arc } ;
4
5
6
+ use fst:: { self , Streamer } ;
7
+ use itertools:: Itertools ;
5
8
use ra_db:: CrateId ;
6
9
use rustc_hash:: FxHashMap ;
7
10
@@ -21,9 +24,17 @@ use crate::{
21
24
///
22
25
/// Note that all paths are relative to the containing crate's root, so the crate name still needs
23
26
/// to be prepended to the `ModPath` before the path is valid.
24
- #[ derive( Eq , PartialEq ) ]
25
27
pub struct ImportMap {
26
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
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
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
146
impl fmt:: Debug for ImportMap {
101
147
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
102
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
250
#[ cfg( test) ]
121
251
mod tests {
122
252
use super :: * ;
123
253
use crate :: test_db:: TestDB ;
124
254
use insta:: assert_snapshot;
125
255
use ra_db:: fixture:: WithFixture ;
126
- use ra_db:: SourceDatabase ;
256
+ use ra_db:: { SourceDatabase , Upcast } ;
127
257
128
258
fn import_map ( ra_fixture : & str ) -> String {
129
259
let db = TestDB :: with_files ( ra_fixture) ;
@@ -144,6 +274,40 @@ mod tests {
144
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
311
#[ test]
148
312
fn smoke ( ) {
149
313
let map = import_map (
@@ -328,4 +492,87 @@ mod tests {
328
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
}
0 commit comments