@@ -12,39 +12,64 @@ pub use type_tree::TypeTree;
12
12
13
13
mod tactics;
14
14
15
+ /// # Maximum amount of variations to take per type
16
+ ///
17
+ /// This is to speed up term search as there may be huge amount of variations of arguments for
18
+ /// function, even when the return type is always the same. The idea is to take first n and call it
19
+ /// a day.
15
20
const MAX_VARIATIONS : usize = 10 ;
16
21
22
+ /// Key for lookup table to query new types reached.
17
23
#[ derive( Debug , Hash , PartialEq , Eq ) ]
18
24
enum NewTypesKey {
19
25
ImplMethod ,
20
26
StructProjection ,
21
27
}
22
28
23
- /// Lookup table for term search
29
+ /// # Lookup table for term search
30
+ ///
31
+ /// Lookup table keeps all the state during term search.
32
+ /// This means it knows what types and how are reachable.
33
+ ///
34
+ /// The secondary functionality for lookup table is to keep track of new types reached since last
35
+ /// iteration as well as keeping track of which `ScopeDef` items have been used.
36
+ /// Both of them are to speed up the term search by leaving out types / ScopeDefs that likely do
37
+ /// not produce any new results.
24
38
#[ derive( Default , Debug ) ]
25
39
struct LookupTable {
40
+ /// All the `TypeTree`s in "value" produce the type of "key"
26
41
data : FxHashMap < Type , FxHashSet < TypeTree > > ,
42
+ /// New types reached since last query by the `NewTypesKey`
27
43
new_types : FxHashMap < NewTypesKey , Vec < Type > > ,
44
+ /// ScopeDefs that are not interesting any more
28
45
exhausted_scopedefs : FxHashSet < ScopeDef > ,
46
+ /// ScopeDefs that were used in current round
29
47
round_scopedef_hits : FxHashSet < ScopeDef > ,
30
- scopedef_hits : FxHashMap < ScopeDef , u32 > ,
48
+ /// Amount of rounds since scopedef was first used.
49
+ rounds_since_sopedef_hit : FxHashMap < ScopeDef , u32 > ,
31
50
}
32
51
33
52
impl LookupTable {
53
+ /// Initialize lookup table
34
54
fn new ( ) -> Self {
35
55
let mut res: Self = Default :: default ( ) ;
36
56
res. new_types . insert ( NewTypesKey :: ImplMethod , Vec :: new ( ) ) ;
37
57
res. new_types . insert ( NewTypesKey :: StructProjection , Vec :: new ( ) ) ;
38
58
res
39
59
}
40
60
61
+ /// Find all `TypeTree`s that unify with the `ty`
41
62
fn find ( & self , db : & dyn HirDatabase , ty : & Type ) -> Option < Vec < TypeTree > > {
42
63
self . data
43
64
. iter ( )
44
65
. find ( |( t, _) | t. could_unify_with_deeply ( db, ty) )
45
66
. map ( |( _, tts) | tts. iter ( ) . cloned ( ) . collect ( ) )
46
67
}
47
68
69
+ /// Same as find but automatically creates shared reference of types in the lookup
70
+ ///
71
+ /// For example if we have type `i32` in data and we query for `&i32` it map all the type
72
+ /// trees we have for `i32` with `TypeTree::Reference` and returns them.
48
73
fn find_autoref ( & self , db : & dyn HirDatabase , ty : & Type ) -> Option < Vec < TypeTree > > {
49
74
self . data
50
75
. iter ( )
@@ -62,6 +87,11 @@ impl LookupTable {
62
87
} )
63
88
}
64
89
90
+ /// Insert new type trees for type
91
+ ///
92
+ /// Note that the types have to be the same, unification is not enough as unification is not
93
+ /// transitive. For example Vec<i32> and FxHashSet<i32> both unify with Iterator<Item = i32>,
94
+ /// but they clearly do not unify themselves.
65
95
fn insert ( & mut self , ty : Type , trees : impl Iterator < Item = TypeTree > ) {
66
96
match self . data . get_mut ( & ty) {
67
97
Some ( it) => it. extend ( trees. take ( MAX_VARIATIONS ) ) ,
@@ -74,28 +104,39 @@ impl LookupTable {
74
104
}
75
105
}
76
106
107
+ /// Iterate all the reachable types
77
108
fn iter_types ( & self ) -> impl Iterator < Item = Type > + ' _ {
78
109
self . data . keys ( ) . cloned ( )
79
110
}
80
111
112
+ /// Query new types reached since last query by key
113
+ ///
114
+ /// Create new key if you wish to query it to avoid conflicting with existing queries.
81
115
fn new_types ( & mut self , key : NewTypesKey ) -> Vec < Type > {
82
116
match self . new_types . get_mut ( & key) {
83
117
Some ( it) => std:: mem:: take ( it) ,
84
118
None => Vec :: new ( ) ,
85
119
}
86
120
}
87
121
122
+ /// Mark `ScopeDef` as exhausted meaning it is not interesting for us any more
88
123
fn mark_exhausted ( & mut self , def : ScopeDef ) {
89
124
self . exhausted_scopedefs . insert ( def) ;
90
125
}
91
126
127
+ /// Mark `ScopeDef` as used meaning we managed to produce something useful from it
92
128
fn mark_fulfilled ( & mut self , def : ScopeDef ) {
93
129
self . round_scopedef_hits . insert ( def) ;
94
130
}
95
131
132
+ /// Start new round (meant to be called at the beginning of iteration in `term_search`)
133
+ ///
134
+ /// This functions marks some `ScopeDef`s as exhausted if there have been
135
+ /// `MAX_ROUNDS_AFTER_HIT` rounds after first using a `ScopeDef`.
96
136
fn new_round ( & mut self ) {
97
137
for def in & self . round_scopedef_hits {
98
- let hits = self . scopedef_hits . entry ( * def) . and_modify ( |n| * n += 1 ) . or_insert ( 0 ) ;
138
+ let hits =
139
+ self . rounds_since_sopedef_hit . entry ( * def) . and_modify ( |n| * n += 1 ) . or_insert ( 0 ) ;
99
140
const MAX_ROUNDS_AFTER_HIT : u32 = 2 ;
100
141
if * hits > MAX_ROUNDS_AFTER_HIT {
101
142
self . exhausted_scopedefs . insert ( * def) ;
@@ -104,6 +145,7 @@ impl LookupTable {
104
145
self . round_scopedef_hits . clear ( ) ;
105
146
}
106
147
148
+ /// Get exhausted `ScopeDef`s
107
149
fn exhausted_scopedefs ( & self ) -> & FxHashSet < ScopeDef > {
108
150
& self . exhausted_scopedefs
109
151
}
@@ -117,6 +159,22 @@ impl LookupTable {
117
159
/// * `sema` - Semantics for the program
118
160
/// * `scope` - Semantic scope, captures context for the term search
119
161
/// * `goal` - Target / expected output type
162
+ ///
163
+ /// Internally this function uses Breadth First Search to find path to `goal` type.
164
+ /// The general idea is following:
165
+ /// 1. Populate lookup (frontier for BFS) from values (local variables, statics, constants, etc)
166
+ /// as well as from well knows values (such as `true/false` and `()`)
167
+ /// 2. Iteratively expand the frontier (or contents of the lookup) by trying different type
168
+ /// transformation tactics. For example functions take as from set of types (arguments) to some
169
+ /// type (return type). Other transformations include methods on type, type constructors and
170
+ /// projections to struct fields (field access).
171
+ /// 3. Once we manage to find path to type we are interested in we continue for single round to see
172
+ /// if we can find more paths that take us to the `goal` type.
173
+ /// 4. Return all the paths (type trees) that take us to the `goal` type.
174
+ ///
175
+ /// Note that there are usually more ways we can get to the `goal` type but some are discarded to
176
+ /// reduce the memory consumption. It is also unlikely anyone is willing ti browse through
177
+ /// thousands of possible responses so we currently take first 10 from every tactic.
120
178
pub fn term_search < DB : HirDatabase > (
121
179
sema : & Semantics < ' _ , DB > ,
122
180
scope : & SemanticsScope < ' _ > ,
@@ -135,6 +193,7 @@ pub fn term_search<DB: HirDatabase>(
135
193
// Try trivial tactic first, also populates lookup table
136
194
let mut solutions: Vec < TypeTree > =
137
195
tactics:: trivial ( sema. db , & defs, & mut lookup, goal) . collect ( ) ;
196
+ // Use well known types tactic before iterations as it does not depend on other tactics
138
197
solutions. extend ( tactics:: famous_types ( sema. db , & module, & defs, & mut lookup, goal) ) ;
139
198
140
199
let mut solution_found = !solutions. is_empty ( ) ;
@@ -147,12 +206,14 @@ pub fn term_search<DB: HirDatabase>(
147
206
solutions. extend ( tactics:: impl_method ( sema. db , & module, & defs, & mut lookup, goal) ) ;
148
207
solutions. extend ( tactics:: struct_projection ( sema. db , & module, & defs, & mut lookup, goal) ) ;
149
208
209
+ // Break after 1 round after successful solution
150
210
if solution_found {
151
211
break ;
152
212
}
153
213
154
214
solution_found = !solutions. is_empty ( ) ;
155
215
216
+ // Discard not interesting `ScopeDef`s for speedup
156
217
for def in lookup. exhausted_scopedefs ( ) {
157
218
defs. remove ( def) ;
158
219
}
0 commit comments