rust/crates/hir/src/term_search/mod.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

299 lines
11 KiB
Rust
Raw Normal View History

2023-12-11 09:04:20 -06:00
//! Term search
use hir_def::type_ref::Mutability;
use hir_ty::db::HirDatabase;
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{ModuleDef, ScopeDef, Semantics, SemanticsScope, Type};
2024-01-06 07:17:16 -06:00
mod expr;
pub use expr::Expr;
2023-12-11 09:04:20 -06:00
mod tactics;
2023-12-12 08:34:31 -06:00
/// Key for lookup table to query new types reached.
2023-12-11 09:04:20 -06:00
#[derive(Debug, Hash, PartialEq, Eq)]
enum NewTypesKey {
ImplMethod,
StructProjection,
}
2024-01-06 07:17:16 -06:00
/// Helper enum to squash big number of alternative trees into `Many` variant as there is too many
/// to take into account.
#[derive(Debug)]
2024-01-06 07:17:16 -06:00
enum AlternativeExprs {
/// There are few trees, so we keep track of them all
Few(FxHashSet<Expr>),
/// There are too many trees to keep track of
Many,
}
2024-01-06 07:17:16 -06:00
impl AlternativeExprs {
/// Construct alternative trees
///
/// # Arguments
/// `threshold` - threshold value for many trees (more than that is many)
/// `exprs` - expressions iterator
fn new(threshold: usize, exprs: impl Iterator<Item = Expr>) -> AlternativeExprs {
let mut it = AlternativeExprs::Few(Default::default());
it.extend_with_threshold(threshold, exprs);
it
}
2024-01-06 07:17:16 -06:00
/// Get type trees stored in alternative trees (or `Expr::Many` in case of many)
///
/// # Arguments
/// `ty` - Type of expressions queried (this is used to give type to `Expr::Many`)
fn exprs(&self, ty: &Type) -> Vec<Expr> {
match self {
2024-01-06 07:17:16 -06:00
AlternativeExprs::Few(exprs) => exprs.iter().cloned().collect(),
AlternativeExprs::Many => vec![Expr::Many(ty.clone())],
}
}
2024-01-06 07:17:16 -06:00
/// Extend alternative expressions
///
/// # Arguments
/// `threshold` - threshold value for many trees (more than that is many)
/// `exprs` - expressions iterator
fn extend_with_threshold(&mut self, threshold: usize, mut exprs: impl Iterator<Item = Expr>) {
match self {
2024-01-06 07:17:16 -06:00
AlternativeExprs::Few(tts) => {
while let Some(it) = exprs.next() {
if tts.len() > threshold {
2024-01-06 07:17:16 -06:00
*self = AlternativeExprs::Many;
break;
}
tts.insert(it);
}
}
2024-01-06 07:17:16 -06:00
AlternativeExprs::Many => (),
}
}
}
2023-12-12 08:34:31 -06:00
/// # Lookup table for term search
///
/// Lookup table keeps all the state during term search.
/// This means it knows what types and how are reachable.
///
/// The secondary functionality for lookup table is to keep track of new types reached since last
/// iteration as well as keeping track of which `ScopeDef` items have been used.
/// Both of them are to speed up the term search by leaving out types / ScopeDefs that likely do
/// not produce any new results.
2023-12-11 09:04:20 -06:00
#[derive(Default, Debug)]
struct LookupTable {
2024-01-06 07:17:16 -06:00
/// All the `Expr`s in "value" produce the type of "key"
data: FxHashMap<Type, AlternativeExprs>,
2023-12-12 08:34:31 -06:00
/// New types reached since last query by the `NewTypesKey`
2023-12-11 09:04:20 -06:00
new_types: FxHashMap<NewTypesKey, Vec<Type>>,
2023-12-12 08:34:31 -06:00
/// ScopeDefs that are not interesting any more
2023-12-11 09:04:20 -06:00
exhausted_scopedefs: FxHashSet<ScopeDef>,
2023-12-12 08:34:31 -06:00
/// ScopeDefs that were used in current round
2023-12-11 09:04:20 -06:00
round_scopedef_hits: FxHashSet<ScopeDef>,
2023-12-12 08:34:31 -06:00
/// Amount of rounds since scopedef was first used.
rounds_since_sopedef_hit: FxHashMap<ScopeDef, u32>,
2023-12-16 08:29:23 -06:00
/// Types queried but not present
types_wishlist: FxHashSet<Type>,
/// Threshold to squash trees to `Many`
many_threshold: usize,
2023-12-11 09:04:20 -06:00
}
impl LookupTable {
2023-12-12 08:34:31 -06:00
/// Initialize lookup table
2024-01-06 07:17:16 -06:00
fn new(many_threshold: usize) -> Self {
let mut res = Self { many_threshold, ..Default::default() };
2023-12-11 09:04:20 -06:00
res.new_types.insert(NewTypesKey::ImplMethod, Vec::new());
res.new_types.insert(NewTypesKey::StructProjection, Vec::new());
res
}
2024-01-06 07:17:16 -06:00
/// Find all `Expr`s that unify with the `ty`
fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
2023-12-11 09:04:20 -06:00
self.data
.iter()
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
2024-01-06 07:17:16 -06:00
.map(|(t, tts)| tts.exprs(t))
2023-12-11 09:04:20 -06:00
}
2023-12-12 08:34:31 -06:00
/// Same as find but automatically creates shared reference of types in the lookup
///
/// For example if we have type `i32` in data and we query for `&i32` it map all the type
2024-01-06 07:17:16 -06:00
/// trees we have for `i32` with `Expr::Reference` and returns them.
fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
2023-12-11 09:04:20 -06:00
self.data
.iter()
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
2024-01-06 07:17:16 -06:00
.map(|(t, it)| it.exprs(t))
2023-12-11 09:04:20 -06:00
.or_else(|| {
self.data
.iter()
.find(|(t, _)| {
Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, &ty)
})
2024-01-06 07:17:16 -06:00
.map(|(t, it)| {
it.exprs(t)
.into_iter()
2024-01-06 07:17:16 -06:00
.map(|expr| Expr::Reference(Box::new(expr)))
.collect()
2023-12-11 09:04:20 -06:00
})
})
}
2023-12-12 08:34:31 -06:00
/// Insert new type trees for type
///
/// Note that the types have to be the same, unification is not enough as unification is not
/// transitive. For example Vec<i32> and FxHashSet<i32> both unify with Iterator<Item = i32>,
/// but they clearly do not unify themselves.
2024-01-06 07:17:16 -06:00
fn insert(&mut self, ty: Type, exprs: impl Iterator<Item = Expr>) {
2023-12-11 09:04:20 -06:00
match self.data.get_mut(&ty) {
2024-01-06 07:17:16 -06:00
Some(it) => it.extend_with_threshold(self.many_threshold, exprs),
2023-12-11 09:04:20 -06:00
None => {
2024-01-06 07:17:16 -06:00
self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs));
2023-12-11 09:04:20 -06:00
for it in self.new_types.values_mut() {
it.push(ty.clone());
}
}
}
}
2023-12-12 08:34:31 -06:00
/// Iterate all the reachable types
2023-12-11 09:04:20 -06:00
fn iter_types(&self) -> impl Iterator<Item = Type> + '_ {
self.data.keys().cloned()
}
2023-12-12 08:34:31 -06:00
/// Query new types reached since last query by key
///
/// Create new key if you wish to query it to avoid conflicting with existing queries.
2023-12-11 09:04:20 -06:00
fn new_types(&mut self, key: NewTypesKey) -> Vec<Type> {
match self.new_types.get_mut(&key) {
Some(it) => std::mem::take(it),
None => Vec::new(),
}
}
2023-12-12 08:34:31 -06:00
/// Mark `ScopeDef` as exhausted meaning it is not interesting for us any more
2023-12-11 09:04:20 -06:00
fn mark_exhausted(&mut self, def: ScopeDef) {
self.exhausted_scopedefs.insert(def);
}
2023-12-12 08:34:31 -06:00
/// Mark `ScopeDef` as used meaning we managed to produce something useful from it
2023-12-11 09:04:20 -06:00
fn mark_fulfilled(&mut self, def: ScopeDef) {
self.round_scopedef_hits.insert(def);
}
2023-12-12 08:34:31 -06:00
/// Start new round (meant to be called at the beginning of iteration in `term_search`)
///
/// This functions marks some `ScopeDef`s as exhausted if there have been
/// `MAX_ROUNDS_AFTER_HIT` rounds after first using a `ScopeDef`.
2023-12-11 09:04:20 -06:00
fn new_round(&mut self) {
for def in &self.round_scopedef_hits {
2023-12-12 08:34:31 -06:00
let hits =
self.rounds_since_sopedef_hit.entry(*def).and_modify(|n| *n += 1).or_insert(0);
2023-12-11 09:04:20 -06:00
const MAX_ROUNDS_AFTER_HIT: u32 = 2;
if *hits > MAX_ROUNDS_AFTER_HIT {
self.exhausted_scopedefs.insert(*def);
}
}
self.round_scopedef_hits.clear();
}
2023-12-12 08:34:31 -06:00
/// Get exhausted `ScopeDef`s
2023-12-11 09:04:20 -06:00
fn exhausted_scopedefs(&self) -> &FxHashSet<ScopeDef> {
&self.exhausted_scopedefs
}
2023-12-16 08:29:23 -06:00
/// Types queried but not found
2023-12-16 08:29:23 -06:00
fn take_types_wishlist(&mut self) -> FxHashSet<Type> {
std::mem::take(&mut self.types_wishlist)
}
2023-12-11 09:04:20 -06:00
}
/// Context for the `term_search` function
2024-01-06 07:17:16 -06:00
#[derive(Debug)]
pub struct TermSearchCtx<'a, DB: HirDatabase> {
/// Semantics for the program
pub sema: &'a Semantics<'a, DB>,
/// Semantic scope, captures context for the term search
pub scope: &'a SemanticsScope<'a>,
/// Target / expected output type
pub goal: Type,
/// Configuration for term search
pub config: TermSearchConfig,
}
/// Configuration options for the term search
#[derive(Debug, Clone, Copy)]
pub struct TermSearchConfig {
/// Enable borrow checking, this guarantees the outputs of the `term_search` to borrow-check
pub enable_borrowcheck: bool,
/// Indicate when to squash multiple trees to `Many` as there are too many to keep track
pub many_alternatives_threshold: usize,
/// Depth of the search eg. number of cycles to run
pub depth: usize,
}
impl Default for TermSearchConfig {
fn default() -> Self {
2024-01-06 07:17:16 -06:00
Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 6 }
}
}
2023-12-11 09:04:20 -06:00
/// # Term search
///
/// Search for terms (expressions) that unify with the `goal` type.
///
/// # Arguments
2024-01-06 07:17:16 -06:00
/// * `ctx` - Context for term search
2023-12-12 08:34:31 -06:00
///
/// Internally this function uses Breadth First Search to find path to `goal` type.
/// The general idea is following:
/// 1. Populate lookup (frontier for BFS) from values (local variables, statics, constants, etc)
/// as well as from well knows values (such as `true/false` and `()`)
/// 2. Iteratively expand the frontier (or contents of the lookup) by trying different type
/// transformation tactics. For example functions take as from set of types (arguments) to some
/// type (return type). Other transformations include methods on type, type constructors and
/// projections to struct fields (field access).
/// 3. Once we manage to find path to type we are interested in we continue for single round to see
/// if we can find more paths that take us to the `goal` type.
/// 4. Return all the paths (type trees) that take us to the `goal` type.
///
/// Note that there are usually more ways we can get to the `goal` type but some are discarded to
/// reduce the memory consumption. It is also unlikely anyone is willing ti browse through
/// thousands of possible responses so we currently take first 10 from every tactic.
2024-01-06 07:17:16 -06:00
pub fn term_search<DB: HirDatabase>(ctx: &TermSearchCtx<'_, DB>) -> Vec<Expr> {
let module = ctx.scope.module();
2023-12-11 09:04:20 -06:00
let mut defs = FxHashSet::default();
defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(module)));
2023-12-11 09:04:20 -06:00
ctx.scope.process_all_names(&mut |_, def| {
2023-12-11 09:04:20 -06:00
defs.insert(def);
});
2024-01-06 07:17:16 -06:00
let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold);
2023-12-11 09:04:20 -06:00
// Try trivial tactic first, also populates lookup table
2024-01-06 07:17:16 -06:00
let mut solutions: Vec<Expr> = tactics::trivial(ctx, &defs, &mut lookup).collect();
2023-12-12 08:34:31 -06:00
// Use well known types tactic before iterations as it does not depend on other tactics
2024-01-06 07:17:16 -06:00
solutions.extend(tactics::famous_types(ctx, &defs, &mut lookup));
2023-12-11 09:04:20 -06:00
for _ in 0..ctx.config.depth {
2023-12-11 09:04:20 -06:00
lookup.new_round();
2024-01-06 07:17:16 -06:00
solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup));
solutions.extend(tactics::free_function(ctx, &defs, &mut lookup));
solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup));
solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup));
solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup));
2023-12-11 09:04:20 -06:00
2023-12-12 08:34:31 -06:00
// Discard not interesting `ScopeDef`s for speedup
2023-12-11 09:04:20 -06:00
for def in lookup.exhausted_scopedefs() {
defs.remove(def);
}
}
2024-01-06 07:17:16 -06:00
solutions.into_iter().filter(|it| !it.is_many()).unique().collect()
2023-12-11 09:04:20 -06:00
}