Improve generics handling in term search

This commit is contained in:
Tavo Annus 2024-02-25 16:50:34 +02:00
parent 1d3558bfe1
commit 8bd30e9b3f
6 changed files with 136 additions and 73 deletions

View File

@ -3856,6 +3856,11 @@ impl Type {
Type { env: ty.env, ty: TyBuilder::slice(ty.ty) }
}
pub fn new_tuple(krate: CrateId, tys: &[Type]) -> Type {
let tys = tys.iter().map(|it| it.ty.clone());
Type { env: TraitEnvironment::empty(krate), ty: TyBuilder::tuple_with(tys) }
}
pub fn is_unit(&self) -> bool {
matches!(self.ty.kind(Interner), TyKind::Tuple(0, ..))
}

View File

@ -72,6 +72,10 @@ impl AlternativeExprs {
AlternativeExprs::Many => (),
}
}
fn is_many(&self) -> bool {
matches!(self, AlternativeExprs::Many)
}
}
/// # Lookup table for term search
@ -103,27 +107,36 @@ struct LookupTable {
impl LookupTable {
/// Initialize lookup table
fn new(many_threshold: usize) -> Self {
fn new(many_threshold: usize, goal: Type) -> Self {
let mut res = Self { many_threshold, ..Default::default() };
res.new_types.insert(NewTypesKey::ImplMethod, Vec::new());
res.new_types.insert(NewTypesKey::StructProjection, Vec::new());
res.types_wishlist.insert(goal);
res
}
/// Find all `Expr`s that unify with the `ty`
fn find(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
self.data
fn find(&mut self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
let res = self
.data
.iter()
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
.map(|(t, tts)| tts.exprs(t))
.map(|(t, tts)| tts.exprs(t));
if res.is_none() {
self.types_wishlist.insert(ty.clone());
}
res
}
/// 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
/// trees we have for `i32` with `Expr::Reference` and returns them.
fn find_autoref(&self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
self.data
fn find_autoref(&mut self, db: &dyn HirDatabase, ty: &Type) -> Option<Vec<Expr>> {
let res = self
.data
.iter()
.find(|(t, _)| t.could_unify_with_deeply(db, ty))
.map(|(t, it)| it.exprs(t))
@ -139,7 +152,13 @@ impl LookupTable {
.map(|expr| Expr::Reference(Box::new(expr)))
.collect()
})
})
});
if res.is_none() {
self.types_wishlist.insert(ty.clone());
}
res
}
/// Insert new type trees for type
@ -149,7 +168,12 @@ impl LookupTable {
/// but they clearly do not unify themselves.
fn insert(&mut self, ty: Type, exprs: impl Iterator<Item = Expr>) {
match self.data.get_mut(&ty) {
Some(it) => it.extend_with_threshold(self.many_threshold, exprs),
Some(it) => {
it.extend_with_threshold(self.many_threshold, exprs);
if it.is_many() {
self.types_wishlist.remove(&ty);
}
}
None => {
self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs));
for it in self.new_types.values_mut() {
@ -206,8 +230,8 @@ impl LookupTable {
}
/// Types queried but not found
fn take_types_wishlist(&mut self) -> FxHashSet<Type> {
std::mem::take(&mut self.types_wishlist)
fn types_wishlist(&mut self) -> &FxHashSet<Type> {
&self.types_wishlist
}
}
@ -272,7 +296,7 @@ pub fn term_search<DB: HirDatabase>(ctx: &TermSearchCtx<'_, DB>) -> Vec<Expr> {
defs.insert(def);
});
let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold);
let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold, ctx.goal.clone());
// Try trivial tactic first, also populates lookup table
let mut solutions: Vec<Expr> = tactics::trivial(ctx, &defs, &mut lookup).collect();

View File

@ -138,6 +138,8 @@ pub enum Expr {
Variant { variant: Variant, generics: Vec<Type>, params: Vec<Expr> },
/// Struct construction
Struct { strukt: Struct, generics: Vec<Type>, params: Vec<Expr> },
/// Tuple construction
Tuple { ty: Type, params: Vec<Expr> },
/// Struct field access
Field { expr: Box<Expr>, field: Field },
/// Passing type as reference (with `&`)
@ -366,6 +368,18 @@ impl Expr {
let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)))?;
Ok(format!("{prefix}{inner}"))
}
Expr::Tuple { params, .. } => {
let args = params
.iter()
.map(|a| {
a.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude)
})
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
let res = format!("({args})");
Ok(res)
}
Expr::Field { expr, field } => {
if expr.contains_many_in_illegal_pos() {
return Ok(many_formatter(&expr.ty(db)));
@ -420,6 +434,7 @@ impl Expr {
Expr::Struct { strukt, generics, .. } => {
Adt::from(*strukt).ty_with_args(db, generics.iter().cloned())
}
Expr::Tuple { ty, .. } => ty.clone(),
Expr::Field { expr, field } => field.ty_with_args(db, expr.ty(db).type_arguments()),
Expr::Reference(it) => it.ty(db),
Expr::Many(ty) => ty.clone(),

View File

@ -109,7 +109,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
lookup: &mut LookupTable,
parent_enum: Enum,
variant: Variant,
goal: &Type,
config: &TermSearchConfig,
) -> Vec<(Type, Vec<Expr>)> {
// Ignore unstable
@ -143,11 +142,14 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
let non_default_type_params_len =
type_params.iter().filter(|it| it.default(db).is_none()).count();
let enum_ty_shallow = Adt::from(parent_enum).ty(db);
let generic_params = lookup
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.types_wishlist()
.clone()
.into_iter()
.permutations(non_default_type_params_len);
.filter(|ty| ty.could_unify_with(db, &enum_ty_shallow))
.map(|it| it.type_arguments().collect::<Vec<Type>>())
.chain((non_default_type_params_len == 0).then_some(Vec::new()));
generic_params
.filter_map(move |generics| {
@ -155,17 +157,11 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
let mut g = generics.into_iter();
let generics: Vec<_> = type_params
.iter()
.map(|it| it.default(db).unwrap_or_else(|| g.next().expect("No generic")))
.collect();
.map(|it| it.default(db).or_else(|| g.next()))
.collect::<Option<_>>()?;
let enum_ty = Adt::from(parent_enum).ty_with_args(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 config.enable_borrowcheck && enum_ty.contains_reference(db) {
return None;
@ -199,21 +195,37 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
.filter_map(move |def| match def {
ScopeDef::ModuleDef(ModuleDef::Variant(it)) => {
let variant_exprs =
variant_helper(db, lookup, it.parent_enum(db), *it, &ctx.goal, &ctx.config);
variant_helper(db, lookup, it.parent_enum(db), *it, &ctx.config);
if variant_exprs.is_empty() {
return None;
}
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Variant(*it)));
if GenericDef::from(it.parent_enum(db))
.type_or_const_params(db)
.into_iter()
.filter_map(|it| it.as_type_param(db))
.all(|it| it.default(db).is_some())
{
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Variant(*it)));
}
Some(variant_exprs)
}
ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(enum_))) => {
let exprs: Vec<(Type, Vec<Expr>)> = enum_
.variants(db)
.into_iter()
.flat_map(|it| variant_helper(db, lookup, *enum_, it, &ctx.goal, &ctx.config))
.flat_map(|it| variant_helper(db, lookup, *enum_, it, &ctx.config))
.collect();
if !exprs.is_empty() {
if exprs.is_empty() {
return None;
}
if GenericDef::from(*enum_)
.type_or_const_params(db)
.into_iter()
.filter_map(|it| it.as_type_param(db))
.all(|it| it.default(db).is_some())
{
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(*enum_))));
}
@ -249,11 +261,14 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
let non_default_type_params_len =
type_params.iter().filter(|it| it.default(db).is_none()).count();
let struct_ty_shallow = Adt::from(*it).ty(db);
let generic_params = lookup
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.types_wishlist()
.clone()
.into_iter()
.permutations(non_default_type_params_len);
.filter(|ty| ty.could_unify_with(db, &struct_ty_shallow))
.map(|it| it.type_arguments().collect::<Vec<Type>>())
.chain((non_default_type_params_len == 0).then_some(Vec::new()));
let exprs = generic_params
.filter_map(|generics| {
@ -261,22 +276,11 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
let mut g = generics.into_iter();
let generics: Vec<_> = type_params
.iter()
.map(|it| {
it.default(db)
.unwrap_or_else(|| g.next().expect("Missing type param"))
})
.collect();
.map(|it| it.default(db).or_else(|| g.next()))
.collect::<Option<_>>()?;
let struct_ty = Adt::from(*it).ty_with_args(db, generics.iter().cloned());
// 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, &ctx.goal)
{
return None;
}
// Ignore types that have something to do with lifetimes
if ctx.config.enable_borrowcheck && struct_ty.contains_reference(db) {
return None;
@ -309,8 +313,12 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
.collect()
};
lookup
.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(*it))));
if non_default_type_params_len == 0 {
// Fulfilled only if there are no generic parameters
lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(
Adt::Struct(*it),
)));
}
lookup.insert(struct_ty.clone(), struct_exprs.iter().cloned());
Some((struct_ty, struct_exprs))
@ -525,14 +533,17 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
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();
// Double check that we have fully known type
if ty.type_arguments().any(|it| it.contains_unknown()) {
return None;
}
// Ignore bigger number of generics for now as they kill the performance
if non_default_type_params_len > 0 {
let non_default_fn_type_params_len =
fn_type_params.iter().filter(|it| it.default(db).is_none()).count();
// Ignore functions with generics for now as they kill the performance
// Also checking bounds for generics is problematic
if non_default_fn_type_params_len > 0 {
return None;
}
@ -540,23 +551,23 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.into_iter()
.permutations(non_default_type_params_len);
.permutations(non_default_fn_type_params_len);
let exprs: 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) {
let generics: Vec<_> = ty
.type_arguments()
.map(Some)
.chain(fn_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::<Option<_>>()?;
let ret_ty = it.ret_type_with_args(
@ -713,7 +724,8 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
let db = ctx.sema.db;
let module = ctx.scope.module();
lookup
.take_types_wishlist()
.types_wishlist()
.clone()
.into_iter()
.chain(iter::once(ctx.goal.clone()))
.flat_map(|ty| {
@ -768,14 +780,17 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
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();
// Double check that we have fully known type
if ty.type_arguments().any(|it| it.contains_unknown()) {
return None;
}
// Ignore bigger number of generics for now as they kill the performance
if non_default_type_params_len > 1 {
let non_default_fn_type_params_len =
fn_type_params.iter().filter(|it| it.default(db).is_none()).count();
// Ignore functions with generics for now as they kill the performance
// Also checking bounds for generics is problematic
if non_default_fn_type_params_len > 0 {
return None;
}
@ -783,16 +798,16 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
.iter_types()
.collect::<Vec<_>>() // Force take ownership
.into_iter()
.permutations(non_default_type_params_len);
.permutations(non_default_fn_type_params_len);
let exprs: 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) {
let generics: Vec<_> = ty
.type_arguments()
.map(Some)
.chain(fn_type_params.iter().map(|it| match it.default(db) {
Some(ty) => Some(ty),
None => {
let generic = g.next().expect("Missing type param");
@ -802,7 +817,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
// Filter out generics that do not unify due to trait bounds
it.ty(db).could_unify_with(db, &generic).then_some(generic)
}
})
}))
.collect::<Option<_>>()?;
let ret_ty = it.ret_type_with_args(

View File

@ -57,11 +57,14 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
})
.unique();
let macro_name = macro_call.name(ctx.sema.db);
let macro_name = macro_name.display(ctx.sema.db);
for code in paths {
acc.add_group(
&GroupLabel(String::from("Term search")),
AssistId("term_search", AssistKind::Generate),
format!("Replace todo!() with {code}"),
format!("Replace {macro_name}!() with {code}"),
goal_range,
|builder| {
builder.replace(goal_range, code);

View File

@ -2599,6 +2599,7 @@ fn foo() {
expect![[r#"
lc foo [type+local]
ex foo [type]
ex Foo::B [type]
ev Foo::A() [type_could_unify]
ev Foo::B [type_could_unify]
en Foo [type_could_unify]