Skip to content

Commit 6db4b57

Browse files
committed
Add more documentation for term search
1 parent 0bfae8e commit 6db4b57

File tree

3 files changed

+120
-13
lines changed

3 files changed

+120
-13
lines changed

crates/hir/src/term_search/mod.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,64 @@ pub use type_tree::TypeTree;
1212

1313
mod 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.
1520
const MAX_VARIATIONS: usize = 10;
1621

22+
/// Key for lookup table to query new types reached.
1723
#[derive(Debug, Hash, PartialEq, Eq)]
1824
enum 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)]
2539
struct 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

3352
impl 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.
120178
pub 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
}

crates/hir/src/term_search/tactics.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
//! Tactics for term search
2+
//!
3+
//! All the tactics take following arguments
4+
//! * `db` - HIR database
5+
//! * `module` - Module where the term search target location
6+
//! * `defs` - Set of items in scope at term search target location
7+
//! * `lookup` - Lookup table for types
8+
//! * `goal` - Term search target type
9+
//! And they return iterator that yields type trees that unify with the `goal` type.
210
311
use hir_def::generics::TypeOrConstParamData;
412
use hir_ty::db::HirDatabase;
@@ -16,10 +24,21 @@ use crate::term_search::TypeTree;
1624

1725
use super::{LookupTable, NewTypesKey, MAX_VARIATIONS};
1826

19-
/// Trivial tactic
27+
/// # Trivial tactic
2028
///
2129
/// Attempts to fulfill the goal by trying items in scope
22-
/// Also works as a starting point to move all items in scope to lookup table
30+
/// Also works as a starting point to move all items in scope to lookup table.
31+
///
32+
/// # Arguments
33+
/// * `db` - HIR database
34+
/// * `defs` - Set of items in scope at term search target location
35+
/// * `lookup` - Lookup table for types
36+
/// * `goal` - Term search target type
37+
///
38+
/// Returns iterator that yields elements that unify with `goal`.
39+
///
40+
/// _Note that there is no use of calling this tactic in every iteration as the output does not
41+
/// depend on the current state of `lookup`_
2342
pub(super) fn trivial<'a>(
2443
db: &'a dyn HirDatabase,
2544
defs: &'a FxHashSet<ScopeDef>,
@@ -67,10 +86,13 @@ pub(super) fn trivial<'a>(
6786
})
6887
}
6988

70-
/// Type constructor tactic
89+
/// # Type constructor tactic
7190
///
7291
/// Attempts different type constructors for enums and structs in scope
7392
///
93+
/// Updates lookup by new types reached and returns iterator that yields
94+
/// elements that unify with `goal`.
95+
///
7496
/// # Arguments
7597
/// * `db` - HIR database
7698
/// * `module` - Module where the term search target location
@@ -255,9 +277,13 @@ pub(super) fn type_constructor<'a>(
255277
.flatten()
256278
}
257279

258-
/// Free function tactic
280+
/// # Free function tactic
259281
///
260-
/// Attempts to call different functions in scope with parameters from lookup table
282+
/// Attempts to call different functions in scope with parameters from lookup table.
283+
/// Functions that include generics are not used for performance reasons.
284+
///
285+
/// Updates lookup by new types reached and returns iterator that yields
286+
/// elements that unify with `goal`.
261287
///
262288
/// # Arguments
263289
/// * `db` - HIR database
@@ -356,10 +382,15 @@ pub(super) fn free_function<'a>(
356382
.flatten()
357383
}
358384

359-
/// Impl method tactic
385+
/// # Impl method tactic
360386
///
361387
/// Attempts to to call methods on types from lookup table.
362388
/// This includes both functions from direct impl blocks as well as functions from traits.
389+
/// Methods defined in impl blocks that are generic and methods that are themselves have
390+
/// generics are ignored for performance reasons.
391+
///
392+
/// Updates lookup by new types reached and returns iterator that yields
393+
/// elements that unify with `goal`.
363394
///
364395
/// # Arguments
365396
/// * `db` - HIR database
@@ -484,9 +515,12 @@ pub(super) fn impl_method<'a>(
484515
.flatten()
485516
}
486517

487-
/// Struct projection tactic
518+
/// # Struct projection tactic
488519
///
489-
/// Attempts different struct fields
520+
/// Attempts different struct fields (`foo.bar.baz`)
521+
///
522+
/// Updates lookup by new types reached and returns iterator that yields
523+
/// elements that unify with `goal`.
490524
///
491525
/// # Arguments
492526
/// * `db` - HIR database
@@ -522,9 +556,14 @@ pub(super) fn struct_projection<'a>(
522556
.flatten()
523557
}
524558

525-
/// Famous types tactic
559+
/// # Famous types tactic
560+
///
561+
/// Attempts different values of well known types such as `true` or `false`.
562+
///
563+
/// Updates lookup by new types reached and returns iterator that yields
564+
/// elements that unify with `goal`.
526565
///
527-
/// Attempts different values of well known types such as `true` or `false`
566+
/// _Note that there is no point of calling it iteratively as the output is always the same_
528567
///
529568
/// # Arguments
530569
/// * `db` - HIR database

crates/hir/src/term_search/type_tree.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
Struct, StructKind, Trait, Type, Variant,
1010
};
1111

12+
/// Helper function to prefix items with modules when required
1213
fn mod_item_path(db: &dyn HirDatabase, sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String {
1314
// Account for locals shadowing items from module
1415
let name_hit_count = def.name(db).map(|def_name| {
@@ -76,6 +77,11 @@ pub enum TypeTree {
7677
}
7778

7879
impl TypeTree {
80+
/// Generate source code for type tree.
81+
///
82+
/// Note that trait imports are not added to generated code.
83+
/// To make sure that the code is valid, callee has to also ensure that all the traits listed
84+
/// by `traits_used` method are also imported.
7985
pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String {
8086
let db = sema_scope.db;
8187
match self {
@@ -222,6 +228,7 @@ impl TypeTree {
222228
}
223229
}
224230

231+
/// List the traits used in type tree
225232
pub fn traits_used(&self, db: &dyn HirDatabase) -> Vec<Trait> {
226233
let mut res = Vec::new();
227234

0 commit comments

Comments
 (0)