Skip to content

Commit

Permalink
Auto merge of #16687 - kilpkonn:master, r=Veykril
Browse files Browse the repository at this point in the history
feat: Add "make tuple" tactic to term search

Follow up to #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`.
  • Loading branch information
bors committed Feb 27, 2024
2 parents d8c5a61 + a2bf15e commit 6b250a2
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 76 deletions.
9 changes: 8 additions & 1 deletion crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ..))
}
Expand Down Expand Up @@ -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))
}
Expand Down
47 changes: 36 additions & 11 deletions crates/hir/src/term_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ impl AlternativeExprs {
AlternativeExprs::Many => (),
}
}

fn is_many(&self) -> bool {
matches!(self, AlternativeExprs::Many)
}
}

/// # Lookup table for term search
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -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() {
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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();
Expand All @@ -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() {
Expand Down
15 changes: 15 additions & 0 deletions crates/hir/src/term_search/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 `&`)
Expand Down Expand Up @@ -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)));
Expand Down Expand Up @@ -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(),
Expand Down
Loading

0 comments on commit 6b250a2

Please sign in to comment.