@@ -12,39 +12,64 @@ pub use type_tree::TypeTree;
1212
1313mod tactics;
1414
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.
1520const MAX_VARIATIONS : usize = 10 ;
1621
22+ /// Key for lookup table to query new types reached.
1723#[ derive( Debug , Hash , PartialEq , Eq ) ]
1824enum NewTypesKey {
1925 ImplMethod ,
2026 StructProjection ,
2127}
2228
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.
2438#[ derive( Default , Debug ) ]
2539struct LookupTable {
40+ /// All the `TypeTree`s in "value" produce the type of "key"
2641 data : FxHashMap < Type , FxHashSet < TypeTree > > ,
42+ /// New types reached since last query by the `NewTypesKey`
2743 new_types : FxHashMap < NewTypesKey , Vec < Type > > ,
44+ /// ScopeDefs that are not interesting any more
2845 exhausted_scopedefs : FxHashSet < ScopeDef > ,
46+ /// ScopeDefs that were used in current round
2947 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 > ,
3150}
3251
3352impl LookupTable {
53+ /// Initialize lookup table
3454 fn new ( ) -> Self {
3555 let mut res: Self = Default :: default ( ) ;
3656 res. new_types . insert ( NewTypesKey :: ImplMethod , Vec :: new ( ) ) ;
3757 res. new_types . insert ( NewTypesKey :: StructProjection , Vec :: new ( ) ) ;
3858 res
3959 }
4060
61+ /// Find all `TypeTree`s that unify with the `ty`
4162 fn find ( & self , db : & dyn HirDatabase , ty : & Type ) -> Option < Vec < TypeTree > > {
4263 self . data
4364 . iter ( )
4465 . find ( |( t, _) | t. could_unify_with_deeply ( db, ty) )
4566 . map ( |( _, tts) | tts. iter ( ) . cloned ( ) . collect ( ) )
4667 }
4768
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.
4873 fn find_autoref ( & self , db : & dyn HirDatabase , ty : & Type ) -> Option < Vec < TypeTree > > {
4974 self . data
5075 . iter ( )
@@ -62,6 +87,11 @@ impl LookupTable {
6287 } )
6388 }
6489
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.
6595 fn insert ( & mut self , ty : Type , trees : impl Iterator < Item = TypeTree > ) {
6696 match self . data . get_mut ( & ty) {
6797 Some ( it) => it. extend ( trees. take ( MAX_VARIATIONS ) ) ,
@@ -74,28 +104,39 @@ impl LookupTable {
74104 }
75105 }
76106
107+ /// Iterate all the reachable types
77108 fn iter_types ( & self ) -> impl Iterator < Item = Type > + ' _ {
78109 self . data . keys ( ) . cloned ( )
79110 }
80111
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.
81115 fn new_types ( & mut self , key : NewTypesKey ) -> Vec < Type > {
82116 match self . new_types . get_mut ( & key) {
83117 Some ( it) => std:: mem:: take ( it) ,
84118 None => Vec :: new ( ) ,
85119 }
86120 }
87121
122+ /// Mark `ScopeDef` as exhausted meaning it is not interesting for us any more
88123 fn mark_exhausted ( & mut self , def : ScopeDef ) {
89124 self . exhausted_scopedefs . insert ( def) ;
90125 }
91126
127+ /// Mark `ScopeDef` as used meaning we managed to produce something useful from it
92128 fn mark_fulfilled ( & mut self , def : ScopeDef ) {
93129 self . round_scopedef_hits . insert ( def) ;
94130 }
95131
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`.
96136 fn new_round ( & mut self ) {
97137 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 ) ;
99140 const MAX_ROUNDS_AFTER_HIT : u32 = 2 ;
100141 if * hits > MAX_ROUNDS_AFTER_HIT {
101142 self . exhausted_scopedefs . insert ( * def) ;
@@ -104,6 +145,7 @@ impl LookupTable {
104145 self . round_scopedef_hits . clear ( ) ;
105146 }
106147
148+ /// Get exhausted `ScopeDef`s
107149 fn exhausted_scopedefs ( & self ) -> & FxHashSet < ScopeDef > {
108150 & self . exhausted_scopedefs
109151 }
@@ -117,6 +159,22 @@ impl LookupTable {
117159/// * `sema` - Semantics for the program
118160/// * `scope` - Semantic scope, captures context for the term search
119161/// * `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.
120178pub fn term_search < DB : HirDatabase > (
121179 sema : & Semantics < ' _ , DB > ,
122180 scope : & SemanticsScope < ' _ > ,
@@ -135,6 +193,7 @@ pub fn term_search<DB: HirDatabase>(
135193 // Try trivial tactic first, also populates lookup table
136194 let mut solutions: Vec < TypeTree > =
137195 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
138197 solutions. extend ( tactics:: famous_types ( sema. db , & module, & defs, & mut lookup, goal) ) ;
139198
140199 let mut solution_found = !solutions. is_empty ( ) ;
@@ -147,12 +206,14 @@ pub fn term_search<DB: HirDatabase>(
147206 solutions. extend ( tactics:: impl_method ( sema. db , & module, & defs, & mut lookup, goal) ) ;
148207 solutions. extend ( tactics:: struct_projection ( sema. db , & module, & defs, & mut lookup, goal) ) ;
149208
209+ // Break after 1 round after successful solution
150210 if solution_found {
151211 break ;
152212 }
153213
154214 solution_found = !solutions. is_empty ( ) ;
155215
216+ // Discard not interesting `ScopeDef`s for speedup
156217 for def in lookup. exhausted_scopedefs ( ) {
157218 defs. remove ( def) ;
158219 }
0 commit comments