//! Tactics for term search //! //! All the tactics take following arguments //! * `ctx` - Context for the term search //! * `defs` - Set of items in scope at term search target location //! * `lookup` - Lookup table for types //! * `should_continue` - Function that indicates when to stop iterating //! //! And they return iterator that yields type trees that unify with the `goal` type. use std::iter; use hir_ty::{ db::HirDatabase, mir::BorrowKind, next_solver::{DbInterner, Ty}, }; use itertools::Itertools; use rustc_hash::FxHashSet; use rustc_type_ir::inherent::Ty as _; use crate::{ Adt, AssocItem, GenericDef, GenericParam, HasAttrs, HasVisibility, Impl, ModuleDef, ScopeDef, Type, TypeParam, term_search::Expr, }; use super::{LookupTable, NewTypesKey, TermSearchCtx}; /// # Trivial tactic /// /// Attempts to fulfill the goal by trying items in scope /// Also works as a starting point to move all items in scope to lookup table. /// /// # Arguments /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types /// /// Returns iterator that yields elements that unify with `goal`. /// /// _Note that there is no use of calling this tactic in every iteration as the output does not /// depend on the current state of `lookup`_ pub(super) fn trivial<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; defs.iter().filter_map(|def| { let expr = match def { ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(Expr::Const(*it)), ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(Expr::Static(*it)), ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(Expr::ConstParam(*it)), ScopeDef::Local(it) => { if ctx.config.enable_borrowcheck { let borrowck = db.borrowck(it.parent).ok()?; let invalid = borrowck.iter().any(|b| { b.partially_moved.iter().any(|moved| { Some(&moved.local) == b.mir_body.binding_locals.get(it.binding_id) }) || b.borrow_regions.iter().any(|region| { // Shared borrows are fine Some(®ion.local) == b.mir_body.binding_locals.get(it.binding_id) && region.kind != BorrowKind::Shared }) }); if invalid { return None; } } Some(Expr::Local(*it)) } _ => None, }?; let ty = expr.ty(db); lookup.insert(ty.clone(), std::iter::once(expr.clone())); // Don't suggest local references as they are not valid for return if matches!(expr, Expr::Local(_)) && ty.contains_reference(db) && ctx.config.enable_borrowcheck { return None; } ty.could_unify_with_deeply(db, &ctx.goal).then_some(expr) }) } /// # Associated constant tactic /// /// Attempts to fulfill the goal by trying constants defined as associated items. /// Only considers them on types that are in scope. /// /// # Arguments /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types /// /// Returns iterator that yields elements that unify with `goal`. /// /// _Note that there is no use of calling this tactic in every iteration as the output does not /// depend on the current state of `lookup`_ pub(super) fn assoc_const<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; let module = ctx.scope.module(); defs.iter() .filter_map(|def| match def { ScopeDef::ModuleDef(ModuleDef::Adt(it)) => Some(it), _ => None, }) .flat_map(|it| Impl::all_for_type(db, it.ty(db))) .filter(|it| !it.is_unsafe(db)) .flat_map(|it| it.items(db)) .filter(move |it| it.is_visible_from(db, module)) .filter_map(AssocItem::as_const) .filter_map(|it| { if it.attrs(db).is_unstable() { return None; } let expr = Expr::Const(it); let ty = it.ty(db); if ty.contains_unknown() { return None; } lookup.insert(ty.clone(), std::iter::once(expr.clone())); ty.could_unify_with_deeply(db, &ctx.goal).then_some(expr) }) } /// # Data constructor tactic /// /// Attempts different data constructors for enums and structs in scope /// /// Updates lookup by new types reached and returns iterator that yields /// elements that unify with `goal`. /// /// # Arguments /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types /// * `should_continue` - Function that indicates when to stop iterating pub(super) fn data_constructor<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, _defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; let module = ctx.scope.module(); lookup .types_wishlist() .clone() .into_iter() .chain(iter::once(ctx.goal.clone())) .filter_map(|ty| ty.as_adt().map(|adt| (adt, ty))) .filter(|_| should_continue()) .filter_map(move |(adt, ty)| match adt { Adt::Struct(strukt) => { // Ignore unstable or not visible if strukt.is_unstable(db) || !strukt.is_visible_from(db, module) { return None; } let generics = GenericDef::from(strukt); // We currently do not check lifetime bounds so ignore all types that have something to do // with them if !generics.lifetime_params(db).is_empty() { return None; } if ty.contains_unknown() { return None; } // Ignore types that have something to do with lifetimes if ctx.config.enable_borrowcheck && ty.contains_reference(db) { return None; } let fields = strukt.fields(db); // Check if all fields are visible, otherwise we cannot fill them if fields.iter().any(|it| !it.is_visible_from(db, module)) { return None; } let generics: Vec<_> = ty.type_arguments().collect(); // Early exit if some param cannot be filled from lookup let param_exprs: Vec>> = fields .into_iter() .map(|field| lookup.find(db, &field.ty_with_args(db, generics.iter().cloned()))) .collect::>()?; // Note that we need special case for 0 param constructors because of multi cartesian // product let exprs: Vec> = if param_exprs.is_empty() { vec![Expr::Struct { strukt, generics, params: Vec::new() }] } else { param_exprs .into_iter() .multi_cartesian_product() .map(|params| Expr::Struct { strukt, generics: generics.clone(), params }) .collect() }; lookup.insert(ty.clone(), exprs.iter().cloned()); Some((ty, exprs)) } Adt::Enum(enum_) => { // Ignore unstable or not visible if enum_.is_unstable(db) || !enum_.is_visible_from(db, module) { return None; } let generics = GenericDef::from(enum_); // We currently do not check lifetime bounds so ignore all types that have something to do // with them if !generics.lifetime_params(db).is_empty() { return None; } if ty.contains_unknown() { return None; } // Ignore types that have something to do with lifetimes if ctx.config.enable_borrowcheck && ty.contains_reference(db) { return None; } let generics: Vec<_> = ty.type_arguments().collect(); let exprs = enum_ .variants(db) .into_iter() .filter_map(|variant| { // Early exit if some param cannot be filled from lookup let param_exprs: Vec>> = variant .fields(db) .into_iter() .map(|field| { lookup.find(db, &field.ty_with_args(db, generics.iter().cloned())) }) .collect::>()?; // Note that we need special case for 0 param constructors because of multi cartesian // product let variant_exprs: Vec> = if param_exprs.is_empty() { vec![Expr::Variant { variant, generics: generics.clone(), params: Vec::new(), }] } else { param_exprs .into_iter() .multi_cartesian_product() .map(|params| Expr::Variant { variant, generics: generics.clone(), params, }) .collect() }; lookup.insert(ty.clone(), variant_exprs.iter().cloned()); Some(variant_exprs) }) .flatten() .collect(); Some((ty, exprs)) } Adt::Union(_) => None, }) .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } /// # Free function tactic /// /// Attempts to call different functions in scope with parameters from lookup table. /// Functions that include generics are not used for performance reasons. /// /// Updates lookup by new types reached and returns iterator that yields /// elements that unify with `goal`. /// /// # Arguments /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types /// * `should_continue` - Function that indicates when to stop iterating pub(super) fn free_function<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; let module = ctx.scope.module(); defs.iter() .filter_map(move |def| match def { ScopeDef::ModuleDef(ModuleDef::Function(it)) => { let generics = GenericDef::from(*it); // Ignore const params for now let type_params = generics .type_or_const_params(db) .into_iter() .map(|it| it.as_type_param(db)) .collect::>>()?; // Ignore lifetimes as we do not check them if !generics.lifetime_params(db).is_empty() { return None; } // Only account for stable type parameters for now, unstable params can be default // tho, for example in `Box` 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; } let generic_params = lookup .iter_types() .collect::>() // Force take ownership .into_iter() .permutations(non_default_type_params_len); let exprs: Vec<_> = generic_params .filter(|_| should_continue()) .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) => Some(ty), None => { let generic = g.next().expect("Missing type param"); // Filter out generics that do not unify due to trait bounds it.ty(db).could_unify_with(db, &generic).then_some(generic) } }) .collect::>()?; let ret_ty = it.ret_type_with_args(db, generics.iter().cloned()); // Filter out private and unsafe functions if !it.is_visible_from(db, module) || it.is_unsafe_to_call( db, None, crate::Crate::from(ctx.scope.resolver().krate()).edition(db), ) || it.is_unstable(db) || ctx.config.enable_borrowcheck && ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() { return None; } // Early exit if some param cannot be filled from lookup let param_exprs: Vec>> = it .params_without_self_with_args(db, generics.iter().cloned()) .into_iter() .map(|field| { let ty = field.ty(); match ty.is_mutable_reference() { true => None, false => lookup.find_autoref(db, ty), } }) .collect::>()?; // Note that we need special case for 0 param constructors because of multi cartesian // product let fn_exprs: Vec> = if param_exprs.is_empty() { vec![Expr::Function { func: *it, generics, params: Vec::new() }] } else { param_exprs .into_iter() .multi_cartesian_product() .map(|params| Expr::Function { func: *it, generics: generics.clone(), params, }) .collect() }; lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned()); Some((ret_ty, fn_exprs)) }) .collect(); Some(exprs) } _ => None, }) .flatten() .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } /// # Impl method tactic /// /// Attempts to call methods on types from lookup table. /// This includes both functions from direct impl blocks as well as functions from traits. /// Methods defined in impl blocks that are generic and methods that are themselves have /// generics are ignored for performance reasons. /// /// Updates lookup by new types reached and returns iterator that yields /// elements that unify with `goal`. /// /// # Arguments /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types /// * `should_continue` - Function that indicates when to stop iterating pub(super) fn impl_method<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, _defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; let module = ctx.scope.module(); lookup .new_types(NewTypesKey::ImplMethod) .into_iter() .filter(|ty| !ty.type_arguments().any(|it| it.contains_unknown())) .filter(|_| should_continue()) .flat_map(|ty| { Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp)) }) .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(|_| should_continue()) .filter_map(move |(imp, ty, it)| { let fn_generics = GenericDef::from(it); let imp_generics = GenericDef::from(imp); // 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 without 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, None, crate::Crate::from(ctx.scope.resolver().krate()).edition(db), ) || it.is_unstable(db) { return None; } // Ignore functions with generics for now as they kill the performance // Also checking bounds for generics is problematic if !fn_generics.type_or_const_params(db).is_empty() { return None; } let ret_ty = it.ret_type_with_args(db, ty.type_arguments()); // Filter out functions that return references if ctx.config.enable_borrowcheck && 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; } let self_ty = it.self_param(db).expect("No self param").ty_with_args(db, ty.type_arguments()); // Ignore functions that have different self type if !self_ty.autoderef(db).any(|s_ty| ty == s_ty) { return None; } let target_type_exprs = lookup.find(db, &ty).expect("Type not in lookup"); // Early exit if some param cannot be filled from lookup let param_exprs: Vec>> = it .params_without_self_with_args(db, ty.type_arguments()) .into_iter() .map(|field| lookup.find_autoref(db, field.ty())) .collect::>()?; let generics: Vec<_> = ty.type_arguments().collect(); let fn_exprs: Vec> = std::iter::once(target_type_exprs) .chain(param_exprs) .multi_cartesian_product() .map(|params| { let mut params = params.into_iter(); let target = Box::new(params.next().unwrap()); Expr::Method { func: it, generics: generics.clone(), target, params: params.collect(), } }) .collect(); Some((ret_ty, fn_exprs)) }) .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } /// # Struct projection tactic /// /// Attempts different struct fields (`foo.bar.baz`) /// /// Updates lookup by new types reached and returns iterator that yields /// elements that unify with `goal`. /// /// # Arguments /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types /// * `should_continue` - Function that indicates when to stop iterating pub(super) fn struct_projection<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, _defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; let module = ctx.scope.module(); lookup .new_types(NewTypesKey::StructProjection) .into_iter() .map(|ty| (ty.clone(), lookup.find(db, &ty).expect("Expr not in lookup"))) .filter(|_| should_continue()) .flat_map(move |(ty, targets)| { ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| { if !field.is_visible_from(db, module) { return None; } let exprs = targets .clone() .into_iter() .map(move |target| Expr::Field { field, expr: Box::new(target) }); Some((filed_ty, exprs)) }) }) .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } /// # Famous types tactic /// /// Attempts different values of well known types such as `true` or `false`. /// /// Updates lookup by new types reached and returns iterator that yields /// elements that unify with `goal`. /// /// _Note that there is no point of calling it iteratively as the output is always the same_ /// /// # Arguments /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types pub(super) fn famous_types<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, _defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; let module = ctx.scope.module(); let interner = DbInterner::new_no_crate(db); let bool_ty = Ty::new_bool(interner); let unit_ty = Ty::new_unit(interner); [ Expr::FamousType { ty: Type::new(db, module.id, bool_ty), value: "true" }, Expr::FamousType { ty: Type::new(db, module.id, bool_ty), value: "false" }, Expr::FamousType { ty: Type::new(db, module.id, unit_ty), value: "()" }, ] .into_iter() .inspect(|exprs| { lookup.insert(exprs.ty(db), std::iter::once(exprs.clone())); }) .filter(|expr| expr.ty(db).could_unify_with_deeply(db, &ctx.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 /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types /// * `should_continue` - Function that indicates when to stop iterating pub(super) fn impl_static_method<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, _defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; let module = ctx.scope.module(); lookup .types_wishlist() .clone() .into_iter() .chain(iter::once(ctx.goal.clone())) .filter(|ty| !ty.type_arguments().any(|it| it.contains_unknown())) .filter(|_| should_continue()) .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(|_| should_continue()) .filter_map(move |(imp, ty, it)| { let fn_generics = GenericDef::from(it); let imp_generics = GenericDef::from(imp); // 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, None, crate::Crate::from(ctx.scope.resolver().krate()).edition(db), ) || it.is_unstable(db) { return None; } // Ignore functions with generics for now as they kill the performance // Also checking bounds for generics is problematic if !fn_generics.type_or_const_params(db).is_empty() { return None; } let ret_ty = it.ret_type_with_args(db, ty.type_arguments()); // Filter out functions that return references if ctx.config.enable_borrowcheck && ret_ty.contains_reference(db) || ret_ty.is_raw_ptr() { return None; } // Early exit if some param cannot be filled from lookup let param_exprs: Vec>> = it .params_without_self_with_args(db, ty.type_arguments()) .into_iter() .map(|field| lookup.find_autoref(db, field.ty())) .collect::>()?; // Note that we need special case for 0 param constructors because of multi cartesian // product let generics = ty.type_arguments().collect(); let fn_exprs: Vec> = if param_exprs.is_empty() { vec![Expr::Function { func: it, generics, params: Vec::new() }] } else { param_exprs .into_iter() .multi_cartesian_product() .map(|params| Expr::Function { func: it, generics: generics.clone(), params }) .collect() }; lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned()); Some((ret_ty, fn_exprs)) }) .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .flatten() } /// # Make tuple tactic /// /// Attempts to create tuple types if any are listed in types wishlist /// /// Updates lookup by new types reached and returns iterator that yields /// elements that unify with `goal`. /// /// # Arguments /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types /// * `should_continue` - Function that indicates when to stop iterating pub(super) fn make_tuple<'a, 'lt, 'db, DB: HirDatabase>( ctx: &'a TermSearchCtx<'db, DB>, _defs: &'a FxHashSet, lookup: &'lt mut LookupTable<'db>, should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator> + use<'a, 'db, 'lt, DB> { let db = ctx.sema.db; let module = ctx.scope.module(); lookup .types_wishlist() .clone() .into_iter() .filter(|_| should_continue()) .filter(|ty| ty.is_tuple()) .filter_map(move |ty| { // Double check to not contain unknown if ty.contains_unknown() { return None; } // Ignore types that have something to do with lifetimes if ctx.config.enable_borrowcheck && ty.contains_reference(db) { return None; } // Early exit if some param cannot be filled from lookup let param_exprs: Vec>> = ty.type_arguments().map(|field| lookup.find(db, &field)).collect::>()?; let exprs: Vec> = param_exprs .into_iter() .multi_cartesian_product() .filter(|_| should_continue()) .map(|params| { let tys: Vec> = params.iter().map(|it| it.ty(db)).collect(); let tuple_ty = Type::new_tuple(module.krate(db).into(), &tys); let expr = Expr::Tuple { ty: tuple_ty.clone(), params }; lookup.insert(tuple_ty, iter::once(expr.clone())); expr }) .collect(); Some(exprs) }) .flatten() .filter_map(|expr| expr.ty(db).could_unify_with_deeply(db, &ctx.goal).then_some(expr)) }