Improve generics handling in term search
This commit is contained in:
parent
1d3558bfe1
commit
8bd30e9b3f
@ -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, ..))
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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(),
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user