Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir/src/term_search/mod.rs')
| -rw-r--r-- | crates/hir/src/term_search/mod.rs | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/crates/hir/src/term_search/mod.rs b/crates/hir/src/term_search/mod.rs new file mode 100644 index 0000000000..b1e616e004 --- /dev/null +++ b/crates/hir/src/term_search/mod.rs @@ -0,0 +1,162 @@ +//! 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}; + +pub mod type_tree; +pub use type_tree::TypeTree; + +mod tactics; + +const MAX_VARIATIONS: usize = 10; + +#[derive(Debug, Hash, PartialEq, Eq)] +enum NewTypesKey { + ImplMethod, + StructProjection, +} + +/// Lookup table for term search +#[derive(Default, Debug)] +struct LookupTable { + data: FxHashMap<Type, FxHashSet<TypeTree>>, + new_types: FxHashMap<NewTypesKey, Vec<Type>>, + exhausted_scopedefs: FxHashSet<ScopeDef>, + round_scopedef_hits: FxHashSet<ScopeDef>, + scopedef_hits: FxHashMap<ScopeDef, u32>, +} + +impl LookupTable { + fn new() -> Self { + let mut res: Self = Default::default(); + res.new_types.insert(NewTypesKey::ImplMethod, Vec::new()); + res.new_types.insert(NewTypesKey::StructProjection, Vec::new()); + res + } + + fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<TypeTree>> { + self.data + .iter() + .find(|(t, _)| t.could_unify_with_deeply(db, ty)) + .map(|(_, tts)| tts.iter().cloned().collect()) + } + + fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<TypeTree>> { + self.data + .iter() + .find(|(t, _)| t.could_unify_with_deeply(db, ty)) + .map(|(_, tts)| tts.iter().cloned().collect()) + .or_else(|| { + self.data + .iter() + .find(|(t, _)| { + Type::reference(t, Mutability::Shared).could_unify_with_deeply(db, &ty) + }) + .map(|(_, tts)| { + tts.iter().map(|tt| TypeTree::Reference(Box::new(tt.clone()))).collect() + }) + }) + } + + fn insert(&mut self, ty: Type, trees: impl Iterator<Item = TypeTree>) { + match self.data.get_mut(&ty) { + Some(it) => it.extend(trees.take(MAX_VARIATIONS)), + None => { + self.data.insert(ty.clone(), trees.take(MAX_VARIATIONS).collect()); + for it in self.new_types.values_mut() { + it.push(ty.clone()); + } + } + } + } + + fn iter_types(&self) -> impl Iterator<Item = Type> + '_ { + self.data.keys().cloned() + } + + 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(), + } + } + + fn mark_exhausted(&mut self, def: ScopeDef) { + self.exhausted_scopedefs.insert(def); + } + + fn mark_fulfilled(&mut self, def: ScopeDef) { + self.round_scopedef_hits.insert(def); + } + + fn new_round(&mut self) { + for def in &self.round_scopedef_hits { + let hits = self.scopedef_hits.entry(*def).and_modify(|n| *n += 1).or_insert(0); + const MAX_ROUNDS_AFTER_HIT: u32 = 2; + if *hits > MAX_ROUNDS_AFTER_HIT { + self.exhausted_scopedefs.insert(*def); + } + } + self.round_scopedef_hits.clear(); + } + + fn exhausted_scopedefs(&self) -> &FxHashSet<ScopeDef> { + &self.exhausted_scopedefs + } +} + +/// # Term search +/// +/// Search for terms (expressions) that unify with the `goal` type. +/// +/// # Arguments +/// * `sema` - Semantics for the program +/// * `scope` - Semantic scope, captures context for the term search +/// * `goal` - Target / expected output type +pub fn term_search<DB: HirDatabase>( + sema: &Semantics<'_, DB>, + scope: &SemanticsScope<'_>, + goal: &Type, +) -> Vec<TypeTree> { + let mut defs = FxHashSet::default(); + defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(scope.module()))); + + scope.process_all_names(&mut |_, def| { + defs.insert(def); + }); + let module = scope.module(); + + let mut lookup = LookupTable::new(); + + // Try trivial tactic first, also populates lookup table + let mut solutions: Vec<TypeTree> = + tactics::trivial(sema.db, &defs, &mut lookup, goal).collect(); + solutions.extend(tactics::famous_types(sema.db, &module, &defs, &mut lookup, goal)); + + let mut solution_found = !solutions.is_empty(); + + for _ in 0..5 { + lookup.new_round(); + + solutions.extend(tactics::type_constructor(sema.db, &module, &defs, &mut lookup, goal)); + solutions.extend(tactics::free_function(sema.db, &module, &defs, &mut lookup, goal)); + solutions.extend(tactics::impl_method(sema.db, &module, &defs, &mut lookup, goal)); + solutions.extend(tactics::struct_projection(sema.db, &module, &defs, &mut lookup, goal)); + + if solution_found { + break; + } + + solution_found = !solutions.is_empty(); + + for def in lookup.exhausted_scopedefs() { + defs.remove(def); + } + } + + solutions.into_iter().unique().collect() +} |