mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
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:
commit
6b250a22c4
7 changed files with 223 additions and 76 deletions
|
@ -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, ..))
|
||||
}
|
||||
|
@ -4320,8 +4325,10 @@ impl Type {
|
|||
self.ty
|
||||
.strip_references()
|
||||
.as_adt()
|
||||
.map(|(_, substs)| substs)
|
||||
.or_else(|| self.ty.strip_references().as_tuple())
|
||||
.into_iter()
|
||||
.flat_map(|(_, substs)| substs.iter(Interner))
|
||||
.flat_map(|substs| substs.iter(Interner))
|
||||
.filter_map(|arg| arg.ty(Interner).cloned())
|
||||
.map(move |ty| self.derived(ty))
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
@ -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::struct_projection(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
|
||||
for def in lookup.exhausted_scopedefs() {
|
||||
|
|
|
@ -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(
|
||||
|
@ -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))
|
||||
.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))
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -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!(); }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[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)); }"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -453,8 +453,11 @@ impl flags::AnalysisStats {
|
|||
err_idx += 7;
|
||||
let err_code = &err[err_idx..err_idx + 4];
|
||||
match err_code {
|
||||
"0282" => continue, // Byproduct of testing method
|
||||
"0277" if generated.contains(&todo) => continue, // See https://github.com/rust-lang/rust/issues/69882
|
||||
"0282" | "0283" => continue, // Byproduct of testing method
|
||||
"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);
|
||||
|
|
Loading…
Reference in a new issue