Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir/src/lib.rs5
-rw-r--r--crates/hir/src/term_search.rs46
-rw-r--r--crates/hir/src/term_search/expr.rs15
-rw-r--r--crates/hir/src/term_search/tactics.rs137
-rw-r--r--crates/ide-assists/src/handlers/term_search.rs5
-rw-r--r--crates/ide-completion/src/render.rs1
6 files changed, 136 insertions, 73 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index a0237e3b90..14066dee5f 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -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, ..))
}
diff --git a/crates/hir/src/term_search.rs b/crates/hir/src/term_search.rs
index 72762007dc..68244b1272 100644
--- a/crates/hir/src/term_search.rs
+++ b/crates/hir/src/term_search.rs
@@ -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();
diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs
index 254fbe7e2b..2d0c5630e1 100644
--- a/crates/hir/src/term_search/expr.rs
+++ b/crates/hir/src/term_search/expr.rs
@@ -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(),
diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs
index edbf75affe..93a780d470 100644
--- a/crates/hir/src/term_search/tactics.rs
+++ b/crates/hir/src/term_search/tactics.rs
@@ -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(
diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs
index 51a1a406f3..fa32a3bbe7 100644
--- a/crates/ide-assists/src/handlers/term_search.rs
+++ b/crates/ide-assists/src/handlers/term_search.rs
@@ -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);
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 3f374b307f..6d1a5a0bc5 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -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]