Auto merge of #16687 - kilpkonn:master, r=Veykril

feat: Add "make tuple" tactic to term search

Follow up to https://github.com/rust-lang/rust-analyzer/pull/16092

Now term search also supports tuples.
```rust
let a: i32 = 1;
let b: f64 = 0.0;
let c: (i32, (f64, i32)) = todo!(); // Finds (a, (b, a))
```
In addition to new tactic that handles tuples I changed how the generics are handled.
Previously it tried all possible options from types we had in scope but now it only tries useful ones that help us directly towards the goal or at least towards calling some other function.
This changes O(2^n) to O(n^2) where n is amount of rounds which in practice allows using types that take generics for multiple rounds (previously limited to 1). Average case that also used to be exponential is now roughly linear.
This means that deeply nested generics also work.
````rust
// Finds all valid combos, including `Some(Some(Some(...)))`
let a: Option<Option<Option<bool>>> = todo!();
````

_Note that although the complexity is smaller allowing more types with generics the search overall slows down considerably. I hope it's fine tho as the autocomplete is disabled by default and for code actions it's not super slow. Might have to tweak the depth hyper parameter tho_

This resulted in a huge increase of results found (benchmarks on `ripgrep` crate):
Before
````
Tail Expr syntactic hits: 149/1692 (8%)
Tail Exprs found: 749/1692 (44%)
Term search avg time: 18ms
```
After
```
Tail Expr syntactic hits: 291/1692 (17%)
Tail Exprs found: 1253/1692 (74%)
Term search avg time: 139ms
````

Most changes are local to term search except some tuple related stuff on `hir::Type`.
This commit is contained in:
bors 2024-02-27 09:41:14 +00:00
commit 6b250a22c4
7 changed files with 223 additions and 76 deletions

View file

@ -3856,6 +3856,11 @@ impl Type {
Type { env: ty.env, ty: TyBuilder::slice(ty.ty) } 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 { pub fn is_unit(&self) -> bool {
matches!(self.ty.kind(Interner), TyKind::Tuple(0, ..)) matches!(self.ty.kind(Interner), TyKind::Tuple(0, ..))
} }
@ -4320,8 +4325,10 @@ impl Type {
self.ty self.ty
.strip_references() .strip_references()
.as_adt() .as_adt()
.map(|(_, substs)| substs)
.or_else(|| self.ty.strip_references().as_tuple())
.into_iter() .into_iter()
.flat_map(|(_, substs)| substs.iter(Interner)) .flat_map(|substs| substs.iter(Interner))
.filter_map(|arg| arg.ty(Interner).cloned()) .filter_map(|arg| arg.ty(Interner).cloned())
.map(move |ty| self.derived(ty)) .map(move |ty| self.derived(ty))
} }

View file

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

View file

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

View file

@ -109,7 +109,6 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
lookup: &mut LookupTable, lookup: &mut LookupTable,
parent_enum: Enum, parent_enum: Enum,
variant: Variant, variant: Variant,
goal: &Type,
config: &TermSearchConfig, config: &TermSearchConfig,
) -> Vec<(Type, Vec<Expr>)> { ) -> Vec<(Type, Vec<Expr>)> {
// Ignore unstable // Ignore unstable
@ -143,11 +142,14 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
let non_default_type_params_len = let non_default_type_params_len =
type_params.iter().filter(|it| it.default(db).is_none()).count(); 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 let generic_params = lookup
.iter_types() .types_wishlist()
.collect::<Vec<_>>() // Force take ownership .clone()
.into_iter() .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 generic_params
.filter_map(move |generics| { .filter_map(move |generics| {
@ -155,17 +157,11 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
let mut g = generics.into_iter(); let mut g = generics.into_iter();
let generics: Vec<_> = type_params let generics: Vec<_> = type_params
.iter() .iter()
.map(|it| it.default(db).unwrap_or_else(|| g.next().expect("No generic"))) .map(|it| it.default(db).or_else(|| g.next()))
.collect(); .collect::<Option<_>>()?;
let enum_ty = Adt::from(parent_enum).ty_with_args(db, generics.iter().cloned()); 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 // Ignore types that have something to do with lifetimes
if config.enable_borrowcheck && enum_ty.contains_reference(db) { if config.enable_borrowcheck && enum_ty.contains_reference(db) {
return None; return None;
@ -199,21 +195,37 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
.filter_map(move |def| match def { .filter_map(move |def| match def {
ScopeDef::ModuleDef(ModuleDef::Variant(it)) => { ScopeDef::ModuleDef(ModuleDef::Variant(it)) => {
let variant_exprs = 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() { if variant_exprs.is_empty() {
return None; return None;
} }
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))); lookup.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Variant(*it)));
}
Some(variant_exprs) Some(variant_exprs)
} }
ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(enum_))) => { ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Enum(enum_))) => {
let exprs: Vec<(Type, Vec<Expr>)> = enum_ let exprs: Vec<(Type, Vec<Expr>)> = enum_
.variants(db) .variants(db)
.into_iter() .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(); .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_)))); 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 = let non_default_type_params_len =
type_params.iter().filter(|it| it.default(db).is_none()).count(); type_params.iter().filter(|it| it.default(db).is_none()).count();
let struct_ty_shallow = Adt::from(*it).ty(db);
let generic_params = lookup let generic_params = lookup
.iter_types() .types_wishlist()
.collect::<Vec<_>>() // Force take ownership .clone()
.into_iter() .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 let exprs = generic_params
.filter_map(|generics| { .filter_map(|generics| {
@ -261,22 +276,11 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
let mut g = generics.into_iter(); let mut g = generics.into_iter();
let generics: Vec<_> = type_params let generics: Vec<_> = type_params
.iter() .iter()
.map(|it| { .map(|it| it.default(db).or_else(|| g.next()))
it.default(db) .collect::<Option<_>>()?;
.unwrap_or_else(|| g.next().expect("Missing type param"))
})
.collect();
let struct_ty = Adt::from(*it).ty_with_args(db, generics.iter().cloned()); 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 // Ignore types that have something to do with lifetimes
if ctx.config.enable_borrowcheck && struct_ty.contains_reference(db) { if ctx.config.enable_borrowcheck && struct_ty.contains_reference(db) {
return None; return None;
@ -309,8 +313,12 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
.collect() .collect()
}; };
lookup if non_default_type_params_len == 0 {
.mark_fulfilled(ScopeDef::ModuleDef(ModuleDef::Adt(Adt::Struct(*it)))); // 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()); lookup.insert(struct_ty.clone(), struct_exprs.iter().cloned());
Some((struct_ty, struct_exprs)) Some((struct_ty, struct_exprs))
@ -525,14 +533,17 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
return None; return None;
} }
let non_default_type_params_len = imp_type_params // Double check that we have fully known type
.iter() if ty.type_arguments().any(|it| it.contains_unknown()) {
.chain(fn_type_params.iter()) return None;
.filter(|it| it.default(db).is_none()) }
.count();
// Ignore bigger number of generics for now as they kill the performance let non_default_fn_type_params_len =
if non_default_type_params_len > 0 { 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; return None;
} }
@ -540,23 +551,23 @@ pub(super) fn impl_method<'a, DB: HirDatabase>(
.iter_types() .iter_types()
.collect::<Vec<_>>() // Force take ownership .collect::<Vec<_>>() // Force take ownership
.into_iter() .into_iter()
.permutations(non_default_type_params_len); .permutations(non_default_fn_type_params_len);
let exprs: Vec<_> = generic_params let exprs: Vec<_> = generic_params
.filter_map(|generics| { .filter_map(|generics| {
// Insert default type params // Insert default type params
let mut g = generics.into_iter(); let mut g = generics.into_iter();
let generics: Vec<_> = imp_type_params let generics: Vec<_> = ty
.iter() .type_arguments()
.chain(fn_type_params.iter()) .map(Some)
.map(|it| match it.default(db) { .chain(fn_type_params.iter().map(|it| match it.default(db) {
Some(ty) => Some(ty), Some(ty) => Some(ty),
None => { None => {
let generic = g.next().expect("Missing type param"); let generic = g.next().expect("Missing type param");
// Filter out generics that do not unify due to trait bounds // Filter out generics that do not unify due to trait bounds
it.ty(db).could_unify_with(db, &generic).then_some(generic) it.ty(db).could_unify_with(db, &generic).then_some(generic)
} }
}) }))
.collect::<Option<_>>()?; .collect::<Option<_>>()?;
let ret_ty = it.ret_type_with_args( 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 db = ctx.sema.db;
let module = ctx.scope.module(); let module = ctx.scope.module();
lookup lookup
.take_types_wishlist() .types_wishlist()
.clone()
.into_iter() .into_iter()
.chain(iter::once(ctx.goal.clone())) .chain(iter::once(ctx.goal.clone()))
.flat_map(|ty| { .flat_map(|ty| {
@ -768,14 +780,17 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
return None; return None;
} }
let non_default_type_params_len = imp_type_params // Double check that we have fully known type
.iter() if ty.type_arguments().any(|it| it.contains_unknown()) {
.chain(fn_type_params.iter()) return None;
.filter(|it| it.default(db).is_none()) }
.count();
// Ignore bigger number of generics for now as they kill the performance let non_default_fn_type_params_len =
if non_default_type_params_len > 1 { 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; return None;
} }
@ -783,16 +798,16 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
.iter_types() .iter_types()
.collect::<Vec<_>>() // Force take ownership .collect::<Vec<_>>() // Force take ownership
.into_iter() .into_iter()
.permutations(non_default_type_params_len); .permutations(non_default_fn_type_params_len);
let exprs: Vec<_> = generic_params let exprs: Vec<_> = generic_params
.filter_map(|generics| { .filter_map(|generics| {
// Insert default type params // Insert default type params
let mut g = generics.into_iter(); let mut g = generics.into_iter();
let generics: Vec<_> = imp_type_params let generics: Vec<_> = ty
.iter() .type_arguments()
.chain(fn_type_params.iter()) .map(Some)
.map(|it| match it.default(db) { .chain(fn_type_params.iter().map(|it| match it.default(db) {
Some(ty) => Some(ty), Some(ty) => Some(ty),
None => { None => {
let generic = g.next().expect("Missing type param"); 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 // Filter out generics that do not unify due to trait bounds
it.ty(db).could_unify_with(db, &generic).then_some(generic) it.ty(db).could_unify_with(db, &generic).then_some(generic)
} }
}) }))
.collect::<Option<_>>()?; .collect::<Option<_>>()?;
let ret_ty = it.ret_type_with_args( let ret_ty = it.ret_type_with_args(
@ -857,3 +872,61 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>(
.filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs)) .filter_map(|(ty, exprs)| ty.could_unify_with_deeply(db, &ctx.goal).then_some(exprs))
.flatten() .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
pub(super) fn make_tuple<'a, DB: HirDatabase>(
ctx: &'a TermSearchCtx<'a, DB>,
_defs: &'a FxHashSet<ScopeDef>,
lookup: &'a mut LookupTable,
) -> impl Iterator<Item = Expr> + 'a {
let db = ctx.sema.db;
let module = ctx.scope.module();
lookup
.types_wishlist()
.clone()
.into_iter()
.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<Vec<Expr>> =
ty.type_arguments().map(|field| lookup.find(db, &field)).collect::<Option<_>>()?;
let exprs: Vec<Expr> = param_exprs
.into_iter()
.multi_cartesian_product()
.map(|params| {
let tys: Vec<Type> = params.iter().map(|it| it.ty(db)).collect();
let tuple_ty = Type::new_tuple(module.krate().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))
}

View file

@ -57,11 +57,14 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
}) })
.unique(); .unique();
let macro_name = macro_call.name(ctx.sema.db);
let macro_name = macro_name.display(ctx.sema.db);
for code in paths { for code in paths {
acc.add_group( acc.add_group(
&GroupLabel(String::from("Term search")), &GroupLabel(String::from("Term search")),
AssistId("term_search", AssistKind::Generate), AssistId("term_search", AssistKind::Generate),
format!("Replace todo!() with {code}"), format!("Replace {macro_name}!() with {code}"),
goal_range, goal_range,
|builder| { |builder| {
builder.replace(goal_range, code); builder.replace(goal_range, code);
@ -250,4 +253,24 @@ fn g() { let a = &1; let b: f32 = f(a); }"#,
fn g() { let a = &mut 1; let b: f32 = todo$0!(); }"#, fn g() { let a = &mut 1; let b: f32 = todo$0!(); }"#,
) )
} }
#[test]
fn test_tuple_simple() {
check_assist(
term_search,
r#"//- minicore: todo, unimplemented
fn f() { let a = 1; let b = 0.0; let c: (i32, f64) = todo$0!(); }"#,
r#"fn f() { let a = 1; let b = 0.0; let c: (i32, f64) = (a, b); }"#,
)
}
#[test]
fn test_tuple_nested() {
check_assist(
term_search,
r#"//- minicore: todo, unimplemented
fn f() { let a = 1; let b = 0.0; let c: (i32, (i32, f64)) = todo$0!(); }"#,
r#"fn f() { let a = 1; let b = 0.0; let c: (i32, (i32, f64)) = (a, (a, b)); }"#,
)
}
} }

View file

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

View file

@ -453,8 +453,11 @@ impl flags::AnalysisStats {
err_idx += 7; err_idx += 7;
let err_code = &err[err_idx..err_idx + 4]; let err_code = &err[err_idx..err_idx + 4];
match err_code { match err_code {
"0282" => continue, // Byproduct of testing method "0282" | "0283" => continue, // Byproduct of testing method
"0277" if generated.contains(&todo) => continue, // See https://github.com/rust-lang/rust/issues/69882 "0277" | "0308" if generated.contains(&todo) => continue, // See https://github.com/rust-lang/rust/issues/69882
// FIXME: In some rare cases `AssocItem::container_or_implemented_trait` returns `None` for trait methods.
// Generated code is valid in case traits are imported
"0599" if err.contains("the following trait is implemented but not in scope") => continue,
_ => (), _ => (),
} }
bar.println(err); bar.println(err);