Add static method tactic

This commit is contained in:
Tavo Annus 2023-12-16 16:29:23 +02:00
parent 35eb0dbbc0
commit 627255dd5a
8 changed files with 569 additions and 93 deletions

View file

@ -47,6 +47,8 @@ struct LookupTable {
round_scopedef_hits: FxHashSet<ScopeDef>,
/// Amount of rounds since scopedef was first used.
rounds_since_sopedef_hit: FxHashMap<ScopeDef, u32>,
/// Types queried but not present
types_wishlist: FxHashSet<Type>,
}
impl LookupTable {
@ -149,6 +151,10 @@ impl LookupTable {
fn exhausted_scopedefs(&self) -> &FxHashSet<ScopeDef> {
&self.exhausted_scopedefs
}
fn take_types_wishlist(&mut self) -> FxHashSet<Type> {
std::mem::take(&mut self.types_wishlist)
}
}
/// # Term search
@ -205,6 +211,7 @@ pub fn term_search<DB: HirDatabase>(
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));
solutions.extend(tactics::impl_static_method(sema.db, &module, &defs, &mut lookup, goal));
// Break after 1 round after successful solution
if solution_found {

View file

@ -8,7 +8,8 @@
//! * `goal` - Term search target type
//! And they return iterator that yields type trees that unify with the `goal` type.
use hir_def::generics::TypeOrConstParamData;
use std::iter;
use hir_ty::db::HirDatabase;
use hir_ty::mir::BorrowKind;
use hir_ty::TyBuilder;
@ -16,8 +17,8 @@ use itertools::Itertools;
use rustc_hash::FxHashSet;
use crate::{
Adt, AssocItem, Enum, GenericParam, HasVisibility, Impl, Module, ModuleDef, ScopeDef, Type,
Variant,
Adt, AssocItem, Enum, GenericDef, GenericParam, HasVisibility, Impl, Module, ModuleDef,
ScopeDef, Type, Variant,
};
use crate::term_search::TypeTree;
@ -78,7 +79,7 @@ pub(super) fn trivial<'a>(
lookup.insert(ty.clone(), std::iter::once(tt.clone()));
// Don't suggest local references as they are not valid for return
if matches!(tt, TypeTree::Local(_)) && ty.is_reference() {
if matches!(tt, TypeTree::Local(_)) && ty.contains_reference(db) {
return None;
}
@ -113,37 +114,67 @@ pub(super) fn type_constructor<'a>(
variant: Variant,
goal: &Type,
) -> Vec<(Type, Vec<TypeTree>)> {
let generics = db.generic_params(variant.parent_enum(db).id.into());
let generics = GenericDef::from(variant.parent_enum(db));
// Ignore unstable variants
if variant.is_unstable(db) {
return Vec::new();
}
// Ignore enums with const generics
if generics
.type_or_consts
.values()
.any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_)))
{
if !generics.const_params(db).is_empty() {
return Vec::new();
}
// We currently do not check lifetime bounds so ignore all types that have something to do
// with them
if !generics.lifetimes.is_empty() {
if !generics.lifetime_params(db).is_empty() {
return Vec::new();
}
// Only account for stable type parameters for now
let type_params = generics.type_params(db);
// Only account for stable type parameters for now, unstable params can be default
// tho, for example in `Box<T, #[unstable] A: Allocator>`
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
return Vec::new();
}
let non_default_type_params_len =
type_params.iter().filter(|it| it.default(db).is_none()).count();
let generic_params = lookup
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.into_iter()
.permutations(generics.type_or_consts.len());
.permutations(non_default_type_params_len);
generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
let generics: Vec<_> = type_params
.iter()
.map(|it| match it.default(db) {
Some(ty) => ty,
None => g.next().expect("Missing type param"),
})
.collect();
let enum_ty = parent_enum.ty_with_generics(db, generics.iter().cloned());
// Allow types with generics only if they take us straight to goal for
// performance reasons
if !generics.is_empty() && !enum_ty.could_unify_with_deeply(db, goal) {
return None;
}
// Ignore types that have something to do with lifetimes
if enum_ty.contains_reference(db) {
return None;
}
// Early exit if some param cannot be filled from lookup
let param_trees: Vec<Vec<TypeTree>> = variant
.fields(db)
@ -203,33 +234,64 @@ pub(super) fn type_constructor<'a>(
Some(trees)
}
ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(it))) => {
let generics = db.generic_params(it.id.into());
// Ignore unstable
if it.is_unstable(db) {
return None;
}
let generics = GenericDef::from(*it);
// Ignore enums with const generics
if generics
.type_or_consts
.values()
.any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_)))
{
if !generics.const_params(db).is_empty() {
return None;
}
// We currently do not check lifetime bounds so ignore all types that have something to do
// with them
if !generics.lifetimes.is_empty() {
if !generics.lifetime_params(db).is_empty() {
return None;
}
let type_params = generics.type_params(db);
// Only account for stable type parameters for now, unstable params can be default
// tho, for example in `Box<T, #[unstable] A: Allocator>`
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
return None;
}
let non_default_type_params_len =
type_params.iter().filter(|it| it.default(db).is_none()).count();
let generic_params = lookup
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.into_iter()
.permutations(generics.type_or_consts.len());
.permutations(non_default_type_params_len);
let trees = generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
let generics: Vec<_> = type_params
.iter()
.map(|it| match it.default(db) {
Some(ty) => ty,
None => g.next().expect("Missing type param"),
})
.collect();
let struct_ty = it.ty_with_generics(db, generics.iter().cloned());
if !generics.is_empty() && !struct_ty.could_unify_with_deeply(db, goal) {
// Allow types with generics only if they take us straight to goal for
// performance reasons
if non_default_type_params_len != 0
&& struct_ty.could_unify_with_deeply(db, goal)
{
return None;
}
// Ignore types that have something to do with lifetimes
if struct_ty.contains_reference(db) {
return None;
}
let fileds = it.fields(db);
@ -301,20 +363,31 @@ pub(super) fn free_function<'a>(
defs.iter()
.filter_map(|def| match def {
ScopeDef::ModuleDef(ModuleDef::Function(it)) => {
let generics = db.generic_params(it.id.into());
let generics = GenericDef::from(*it);
// Skip functions that require const generics
if generics
.type_or_consts
.values()
.any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_)))
{
if !generics.const_params(db).is_empty() {
return None;
}
// Ignore bigger number of generics for now as they kill the performance
// Ignore lifetimes as we do not check them
if generics.type_or_consts.len() > 0 || !generics.lifetimes.is_empty() {
if !generics.lifetime_params(db).is_empty() {
return None;
}
let type_params = generics.type_params(db);
// Only account for stable type parameters for now, unstable params can be default
// tho, for example in `Box<T, #[unstable] A: Allocator>`
if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
return None;
}
let non_default_type_params_len =
type_params.iter().filter(|it| it.default(db).is_none()).count();
// Ignore bigger number of generics for now as they kill the performance
if non_default_type_params_len > 0 {
return None;
}
@ -322,16 +395,26 @@ pub(super) fn free_function<'a>(
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.into_iter()
.permutations(generics.type_or_consts.len());
.permutations(non_default_type_params_len);
let trees: Vec<_> = generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
let generics: Vec<_> = type_params
.iter()
.map(|it| match it.default(db) {
Some(ty) => ty,
None => g.next().expect("Missing type param"),
})
.collect();
let ret_ty = it.ret_type_with_generics(db, generics.iter().cloned());
// Filter out private and unsafe functions
if !it.is_visible_from(db, *module)
|| it.is_unsafe_to_call(db)
|| it.is_unstable(db)
|| ret_ty.is_reference()
|| ret_ty.contains_reference(db)
|| ret_ty.is_raw_ptr()
{
return None;
@ -417,24 +500,17 @@ pub(super) fn impl_method<'a>(
_ => None,
})
.filter_map(|(imp, ty, it)| {
let fn_generics = db.generic_params(it.id.into());
let imp_generics = db.generic_params(imp.id.into());
let fn_generics = GenericDef::from(it);
let imp_generics = GenericDef::from(imp);
// Ignore impl if it has const type arguments
if fn_generics
.type_or_consts
.values()
.any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_)))
|| imp_generics
.type_or_consts
.values()
.any(|it| matches!(it, TypeOrConstParamData::ConstParamData(_)))
if !fn_generics.const_params(db).is_empty() || !imp_generics.const_params(db).is_empty()
{
return None;
}
// Ignore all functions that have something to do with lifetimes as we don't check them
if !fn_generics.lifetimes.is_empty() {
if !fn_generics.lifetime_params(db).is_empty() {
return None;
}
@ -448,8 +524,25 @@ pub(super) fn impl_method<'a>(
return None;
}
let imp_type_params = imp_generics.type_params(db);
let fn_type_params = fn_generics.type_params(db);
// Only account for stable type parameters for now, unstable params can be default
// tho, for example in `Box<T, #[unstable] A: Allocator>`
if imp_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
|| fn_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
{
return None;
}
let non_default_type_params_len = imp_type_params
.iter()
.chain(fn_type_params.iter())
.filter(|it| it.default(db).is_none())
.count();
// Ignore bigger number of generics for now as they kill the performance
if imp_generics.type_or_consts.len() + fn_generics.type_or_consts.len() > 0 {
if non_default_type_params_len > 0 {
return None;
}
@ -457,16 +550,27 @@ pub(super) fn impl_method<'a>(
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.into_iter()
.permutations(imp_generics.type_or_consts.len() + fn_generics.type_or_consts.len());
.permutations(non_default_type_params_len);
let trees: Vec<_> = generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
let generics: Vec<_> = imp_type_params
.iter()
.chain(fn_type_params.iter())
.map(|it| match it.default(db) {
Some(ty) => ty,
None => g.next().expect("Missing type param"),
})
.collect();
let ret_ty = it.ret_type_with_generics(
db,
ty.type_arguments().chain(generics.iter().cloned()),
);
// Filter out functions that return references
if ret_ty.is_reference() || ret_ty.is_raw_ptr() {
if ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() {
return None;
}
@ -590,3 +694,157 @@ pub(super) fn famous_types<'a>(
})
.filter(|tt| tt.ty(db).could_unify_with_deeply(db, goal))
}
/// # Impl static method (without self type) tactic
///
/// Attempts different functions from impl blocks that take no self parameter.
///
/// Updates lookup by new types reached and returns iterator that yields
/// elements that unify with `goal`.
///
/// # Arguments
/// * `db` - HIR database
/// * `module` - Module where the term search target location
/// * `defs` - Set of items in scope at term search target location
/// * `lookup` - Lookup table for types
/// * `goal` - Term search target type
pub(super) fn impl_static_method<'a>(
db: &'a dyn HirDatabase,
module: &'a Module,
_defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
goal: &'a Type,
) -> impl Iterator<Item = TypeTree> + 'a {
lookup
.take_types_wishlist()
.into_iter()
.chain(iter::once(goal.clone()))
.flat_map(|ty| {
Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp))
})
.filter(|(_, imp)| !imp.is_unsafe(db))
.flat_map(|(ty, imp)| imp.items(db).into_iter().map(move |item| (imp, ty.clone(), item)))
.filter_map(|(imp, ty, it)| match it {
AssocItem::Function(f) => Some((imp, ty, f)),
_ => None,
})
.filter_map(|(imp, ty, it)| {
let fn_generics = GenericDef::from(it);
let imp_generics = GenericDef::from(imp);
// Ignore impl if it has const type arguments
if !fn_generics.const_params(db).is_empty() || !imp_generics.const_params(db).is_empty()
{
return None;
}
// Ignore all functions that have something to do with lifetimes as we don't check them
if !fn_generics.lifetime_params(db).is_empty()
|| !imp_generics.lifetime_params(db).is_empty()
{
return None;
}
// Ignore functions with self param
if it.has_self_param(db) {
return None;
}
// Filter out private and unsafe functions
if !it.is_visible_from(db, *module) || it.is_unsafe_to_call(db) || it.is_unstable(db) {
return None;
}
let imp_type_params = imp_generics.type_params(db);
let fn_type_params = fn_generics.type_params(db);
// Only account for stable type parameters for now, unstable params can be default
// tho, for example in `Box<T, #[unstable] A: Allocator>`
if imp_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
|| fn_type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none())
{
return None;
}
let non_default_type_params_len = imp_type_params
.iter()
.chain(fn_type_params.iter())
.filter(|it| it.default(db).is_none())
.count();
// Ignore bigger number of generics for now as they kill the performance
if non_default_type_params_len > 0 {
return None;
}
let generic_params = lookup
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.into_iter()
.permutations(non_default_type_params_len);
let trees: Vec<_> = generic_params
.filter_map(|generics| {
// Insert default type params
let mut g = generics.into_iter();
let generics: Vec<_> = imp_type_params
.iter()
.chain(fn_type_params.iter())
.map(|it| match it.default(db) {
Some(ty) => ty,
None => g.next().expect("Missing type param"),
})
.collect();
let ret_ty = it.ret_type_with_generics(
db,
ty.type_arguments().chain(generics.iter().cloned()),
);
// Filter out functions that return references
if ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() {
return None;
}
// Ignore functions that do not change the type
// if ty.could_unify_with_deeply(db, &ret_ty) {
// return None;
// }
// Early exit if some param cannot be filled from lookup
let param_trees: Vec<Vec<TypeTree>> = it
.params_without_self_with_generics(
db,
ty.type_arguments().chain(generics.iter().cloned()),
)
.into_iter()
.map(|field| lookup.find_autoref(db, &field.ty()))
.collect::<Option<_>>()?;
// Note that we need special case for 0 param constructors because of multi cartesian
// product
let fn_trees: Vec<TypeTree> = if param_trees.is_empty() {
vec![TypeTree::Function { func: it, generics, params: Vec::new() }]
} else {
param_trees
.into_iter()
.multi_cartesian_product()
.take(MAX_VARIATIONS)
.map(|params| TypeTree::Function {
func: it,
generics: generics.clone(),
params,
})
.collect()
};
lookup.insert(ret_ty.clone(), fn_trees.iter().cloned());
Some((ret_ty, fn_trees))
})
.collect();
Some(trees)
})
.flatten()
.filter_map(|(ty, trees)| ty.could_unify_with_deeply(db, goal).then(|| trees))
.flatten()
}

View file

@ -1,16 +1,18 @@
//! Type tree for term search
use hir_def::find_path::PrefixKind;
use hir_expand::mod_path::ModPath;
use hir_ty::{db::HirDatabase, display::HirDisplay};
use itertools::Itertools;
use crate::{
Adt, AsAssocItem, Const, ConstParam, Field, Function, Local, ModuleDef, SemanticsScope, Static,
Struct, StructKind, Trait, Type, Variant,
Adt, AsAssocItem, Const, ConstParam, Field, Function, GenericDef, Local, ModuleDef,
SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant,
};
/// Helper function to prefix items with modules when required
fn mod_item_path(db: &dyn HirDatabase, sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String {
/// Helper function to get path to `ModuleDef`
fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option<ModPath> {
let db = sema_scope.db;
// Account for locals shadowing items from module
let name_hit_count = def.name(db).map(|def_name| {
let mut name_hit_count = 0;
@ -23,12 +25,45 @@ fn mod_item_path(db: &dyn HirDatabase, sema_scope: &SemanticsScope<'_>, def: &Mo
});
let m = sema_scope.module();
let path = match name_hit_count {
match name_hit_count {
Some(0..=1) | None => m.find_use_path(db.upcast(), *def, false, true),
Some(_) => m.find_use_path_prefixed(db.upcast(), *def, PrefixKind::ByCrate, false, true),
};
}
}
path.map(|it| it.display(db.upcast()).to_string()).expect("use path error")
/// Helper function to get path to `ModuleDef` as string
fn mod_item_path_str(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String {
let path = mod_item_path(sema_scope, def);
path.map(|it| it.display(sema_scope.db.upcast()).to_string()).unwrap()
}
/// Helper function to get path to `Type`
fn type_path(sema_scope: &SemanticsScope<'_>, ty: &Type) -> String {
let db = sema_scope.db;
match ty.as_adt() {
Some(adt) => {
let ty_name = ty.display(db).to_string();
let mut path = mod_item_path(sema_scope, &ModuleDef::Adt(adt)).unwrap();
path.pop_segment();
let path = path.display(db.upcast()).to_string();
match path.is_empty() {
true => ty_name,
false => format!("{path}::{ty_name}"),
}
}
None => ty.display(db).to_string(),
}
}
/// Helper function to filter out generic parameters that are default
fn non_default_generics(db: &dyn HirDatabase, def: GenericDef, generics: &[Type]) -> Vec<Type> {
def.type_params(db)
.into_iter()
.zip(generics)
.filter(|(tp, arg)| tp.default(db).as_ref() != Some(arg))
.map(|(_, arg)| arg.clone())
.collect()
}
/// Type tree shows how can we get from set of types to some type.
@ -85,8 +120,8 @@ impl TypeTree {
pub fn gen_source_code(&self, sema_scope: &SemanticsScope<'_>) -> String {
let db = sema_scope.db;
match self {
TypeTree::Const(it) => mod_item_path(db, sema_scope, &ModuleDef::Const(*it)),
TypeTree::Static(it) => mod_item_path(db, sema_scope, &ModuleDef::Static(*it)),
TypeTree::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)),
TypeTree::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)),
TypeTree::Local(it) => return it.name(db).display(db.upcast()).to_string(),
TypeTree::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(),
TypeTree::FamousType { value, .. } => return value.to_string(),
@ -100,7 +135,7 @@ impl TypeTree {
match func.as_assoc_item(db).unwrap().containing_trait_or_trait_impl(db) {
Some(trait_) => {
let trait_name =
mod_item_path(db, sema_scope, &ModuleDef::Trait(trait_));
mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_));
let target = match self_param.access(db) {
crate::Access::Shared => format!("&{target}"),
crate::Access::Exclusive => format!("&mut {target}"),
@ -116,15 +151,51 @@ impl TypeTree {
} else {
let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", ");
let fn_name = mod_item_path(db, sema_scope, &ModuleDef::Function(*func));
format!("{fn_name}({args})",)
match func.as_assoc_item(db).map(|it| it.container(db)) {
Some(container) => {
let container_name = match container {
crate::AssocItemContainer::Trait(trait_) => {
mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))
}
crate::AssocItemContainer::Impl(imp) => {
let self_ty = imp.self_ty(db);
// Should it be guaranteed that `mod_item_path` always exists?
match self_ty
.as_adt()
.and_then(|adt| mod_item_path(sema_scope, &adt.into()))
{
Some(path) => {
path.display(sema_scope.db.upcast()).to_string()
}
None => self_ty.display(db).to_string(),
}
}
};
let fn_name = func.name(db).display(db.upcast()).to_string();
format!("{container_name}::{fn_name}({args})",)
}
None => {
let fn_name =
mod_item_path_str(sema_scope, &ModuleDef::Function(*func));
format!("{fn_name}({args})",)
}
}
}
}
TypeTree::Variant { variant, generics, params } => {
let generics = non_default_generics(db, (*variant).into(), generics);
let generics_str = match generics.is_empty() {
true => String::new(),
false => {
let generics =
generics.iter().map(|it| type_path(sema_scope, it)).join(", ");
format!("::<{generics}>")
}
};
let inner = match variant.kind(db) {
StructKind::Tuple => {
let args = params.iter().map(|f| f.gen_source_code(sema_scope)).join(", ");
format!("({args})")
format!("{generics_str}({args})")
}
StructKind::Record => {
let fields = variant.fields(db);
@ -139,21 +210,16 @@ impl TypeTree {
)
})
.join(", ");
format!("{{ {args} }}")
format!("{generics_str}{{ {args} }}")
}
StructKind::Unit => match generics.is_empty() {
true => String::new(),
false => {
let generics = generics.iter().map(|it| it.display(db)).join(", ");
format!("::<{generics}>")
}
},
StructKind::Unit => generics_str,
};
let prefix = mod_item_path(db, sema_scope, &ModuleDef::Variant(*variant));
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant));
format!("{prefix}{inner}")
}
TypeTree::Struct { strukt, generics, params } => {
let generics = non_default_generics(db, (*strukt).into(), generics);
let inner = match strukt.kind(db) {
StructKind::Tuple => {
let args = params.iter().map(|a| a.gen_source_code(sema_scope)).join(", ");
@ -177,13 +243,14 @@ impl TypeTree {
StructKind::Unit => match generics.is_empty() {
true => String::new(),
false => {
let generics = generics.iter().map(|it| it.display(db)).join(", ");
let generics =
generics.iter().map(|it| type_path(sema_scope, it)).join(", ");
format!("::<{generics}>")
}
},
};
let prefix = mod_item_path(db, sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)));
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)));
format!("{prefix}{inner}")
}
TypeTree::Field { type_tree, field } => {