From c3ab435b54c3e48d5f09830956098ac03335881a Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Mon, 6 May 2024 08:38:43 +0300 Subject: [PATCH 1/4] Collapse term search exprs before Cartesian product to avoid OOM --- crates/hir/src/term_search.rs | 20 ++++++++++-- crates/hir/src/term_search/expr.rs | 32 ++++++++++++------- crates/ide-completion/src/render.rs | 7 ++-- .../src/handlers/typed_hole.rs | 4 +-- 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/crates/hir/src/term_search.rs b/crates/hir/src/term_search.rs index 93e7300491..ea13b0c8a8 100644 --- a/crates/hir/src/term_search.rs +++ b/crates/hir/src/term_search.rs @@ -127,6 +127,13 @@ impl LookupTable { self.types_wishlist.insert(ty.clone()); } + // Collapse suggestions if there are many + if let Some(res) = &res { + if res.len() > self.many_threshold { + return Some(vec![Expr::Many(ty.clone())]); + } + } + res } @@ -158,6 +165,13 @@ impl LookupTable { self.types_wishlist.insert(ty.clone()); } + // Collapse suggestions if there are many + if let Some(res) = &res { + if res.len() > self.many_threshold { + return Some(vec![Expr::Many(ty.clone())]); + } + } + res } @@ -176,11 +190,11 @@ impl LookupTable { } None => { self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs)); - for it in self.new_types.values_mut() { - it.push(ty.clone()); - } } } + for it in self.new_types.values_mut() { + it.push(ty.clone()); + } } /// Iterate all the reachable types diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs index 2d0c5630e1..06372355c5 100644 --- a/crates/hir/src/term_search/expr.rs +++ b/crates/hir/src/term_search/expr.rs @@ -211,13 +211,13 @@ impl Expr { } } Expr::Method { func, target, params, .. } => { - if target.contains_many_in_illegal_pos() { + if self.contains_many_in_illegal_pos(db) { return Ok(many_formatter(&target.ty(db))); } let func_name = func.name(db).display(db.upcast()).to_string(); let self_param = func.self_param(db).unwrap(); - let target = target.gen_source_code( + let target_str = target.gen_source_code( sema_scope, many_formatter, prefer_no_std, @@ -236,9 +236,12 @@ impl Expr { Some(trait_) => { let trait_name = mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_))?; let target = match self_param.access(db) { - crate::Access::Shared => format!("&{target}"), - crate::Access::Exclusive => format!("&mut {target}"), - crate::Access::Owned => target, + crate::Access::Shared if !target.is_many() => format!("&{target_str}"), + crate::Access::Exclusive if !target.is_many() => { + format!("&mut {target_str}") + } + crate::Access::Owned => target_str, + _ => many_formatter(&target.ty(db)), }; let res = match args.is_empty() { true => format!("{trait_name}::{func_name}({target})",), @@ -246,7 +249,7 @@ impl Expr { }; Ok(res) } - None => Ok(format!("{target}.{func_name}({args})")), + None => Ok(format!("{target_str}.{func_name}({args})")), } } Expr::Variant { variant, generics, params } => { @@ -381,7 +384,7 @@ impl Expr { Ok(res) } Expr::Field { expr, field } => { - if expr.contains_many_in_illegal_pos() { + if expr.contains_many_in_illegal_pos(db) { return Ok(many_formatter(&expr.ty(db))); } @@ -395,7 +398,7 @@ impl Expr { Ok(format!("{strukt}.{field}")) } Expr::Reference(expr) => { - if expr.contains_many_in_illegal_pos() { + if expr.contains_many_in_illegal_pos(db) { return Ok(many_formatter(&expr.ty(db))); } @@ -466,10 +469,17 @@ impl Expr { /// macro!().bar() /// ¯o!() /// ``` - fn contains_many_in_illegal_pos(&self) -> bool { + fn contains_many_in_illegal_pos(&self, db: &dyn HirDatabase) -> bool { match self { - Expr::Method { target, .. } => target.contains_many_in_illegal_pos(), - Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(), + Expr::Method { target, func, .. } => { + match func.as_assoc_item(db).and_then(|it| it.container_or_implemented_trait(db)) { + Some(_) => false, + None => { + target.is_many() + } + } + } + Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(db), Expr::Reference(target) => target.is_many(), Expr::Many(_) => true, _ => false, diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 0f13bea7b6..2b4743cf18 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -1808,8 +1808,7 @@ fn f() { A { bar: b$0 }; } fn baz() [type] ex baz() [type] ex bar() [type] - ex A { bar: baz() }.bar [type] - ex A { bar: bar() }.bar [type] + ex A { bar: ... }.bar [type] st A [] fn f() [] "#]], @@ -1947,8 +1946,8 @@ fn main() { } "#, expect![[r#" - ex core::ops::Deref::deref(&T(S)) (use core::ops::Deref) [type_could_unify] ex core::ops::Deref::deref(&t) (use core::ops::Deref) [type_could_unify] + ex core::ops::Deref::deref(&T(S)) (use core::ops::Deref) [type_could_unify] lc m [local] lc t [local] lc &t [type+local] @@ -1997,8 +1996,8 @@ fn main() { } "#, expect![[r#" - ex core::ops::DerefMut::deref_mut(&mut T(S)) (use core::ops::DerefMut) [type_could_unify] ex core::ops::DerefMut::deref_mut(&mut t) (use core::ops::DerefMut) [type_could_unify] + ex core::ops::DerefMut::deref_mut(&mut T(S)) (use core::ops::DerefMut) [type_could_unify] lc m [local] lc t [local] lc &mut t [type+local] diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index 56c8181e84..e78bd2db8b 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -274,7 +274,7 @@ impl Foo for Baz { } fn asd() -> Bar { let a = Baz; - Foo::foo(a) + Foo::foo(_) } ", ); @@ -363,7 +363,7 @@ impl Foo for A { } fn main() { let a = A; - let c: Bar = Foo::foo(&a); + let c: Bar = Foo::foo(_); }"#, ); } From d253617bbad8fe5c646eecee82441ebe89e5a1da Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Mon, 6 May 2024 21:56:36 +0300 Subject: [PATCH 2/4] Add time based fuel to term search --- crates/hir/src/term_search.rs | 41 +++++++++++++++---- crates/ide-completion/src/completions/expr.rs | 3 +- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/crates/hir/src/term_search.rs b/crates/hir/src/term_search.rs index ea13b0c8a8..18e6369bfa 100644 --- a/crates/hir/src/term_search.rs +++ b/crates/hir/src/term_search.rs @@ -1,5 +1,7 @@ //! Term search +use std::time::{Duration, Instant}; + use hir_def::type_ref::Mutability; use hir_ty::db::HirDatabase; use itertools::Itertools; @@ -190,11 +192,11 @@ impl LookupTable { } None => { self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs)); + for it in self.new_types.values_mut() { + it.push(ty.clone()); + } } } - for it in self.new_types.values_mut() { - it.push(ty.clone()); - } } /// Iterate all the reachable types @@ -269,13 +271,20 @@ pub struct TermSearchConfig { pub enable_borrowcheck: bool, /// Indicate when to squash multiple trees to `Many` as there are too many to keep track pub many_alternatives_threshold: usize, - /// Depth of the search eg. number of cycles to run + /// Depth of the search i.e. number of cycles to run pub depth: usize, + /// Time fuel for term search + pub fuel: Option, } impl Default for TermSearchConfig { fn default() -> Self { - Self { enable_borrowcheck: true, many_alternatives_threshold: 1, depth: 6 } + Self { + enable_borrowcheck: true, + many_alternatives_threshold: 1, + depth: 5, + fuel: Some(Duration::from_millis(100)), + } } } @@ -294,8 +303,7 @@ impl Default for TermSearchConfig { /// transformation tactics. For example functions take as from set of types (arguments) to some /// type (return type). Other transformations include methods on type, type constructors and /// projections to struct fields (field access). -/// 3. Once we manage to find path to type we are interested in we continue for single round to see -/// if we can find more paths that take us to the `goal` type. +/// 3. If we run out of fuel (term search takes too long) we stop iterating. /// 4. Return all the paths (type trees) that take us to the `goal` type. /// /// Note that there are usually more ways we can get to the `goal` type but some are discarded to @@ -311,6 +319,7 @@ pub fn term_search(ctx: &TermSearchCtx<'_, DB>) -> Vec { }); let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold, ctx.goal.clone()); + let start = Instant::now(); // Try trivial tactic first, also populates lookup table let mut solutions: Vec = tactics::trivial(ctx, &defs, &mut lookup).collect(); @@ -320,11 +329,29 @@ pub fn term_search(ctx: &TermSearchCtx<'_, DB>) -> Vec { for _ in 0..ctx.config.depth { lookup.new_round(); + if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { + break; + } solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup)); + if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { + break; + } solutions.extend(tactics::free_function(ctx, &defs, &mut lookup)); + if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { + break; + } solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup)); + if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { + break; + } solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup)); + if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { + break; + } solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup)); + if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { + break; + } solutions.extend(tactics::make_tuple(ctx, &defs, &mut lookup)); // Discard not interesting `ScopeDef`s for speedup diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 802e9bc3a8..e91cbc867b 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -353,7 +353,8 @@ pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) config: hir::term_search::TermSearchConfig { enable_borrowcheck: false, many_alternatives_threshold: 1, - depth: 6, + depth: 3, + ..Default::default() }, }; let exprs = hir::term_search::term_search(&term_search_ctx); From 9e1adc76e57ea37bbfd126f2f6826218cd4659e3 Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Tue, 7 May 2024 22:11:33 +0300 Subject: [PATCH 3/4] Use unit of work as fuel instead of time --- crates/hir/src/term_search.rs | 58 +++++++------------ crates/hir/src/term_search/tactics.rs | 30 +++++++++- crates/ide-completion/src/completions/expr.rs | 2 +- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/crates/hir/src/term_search.rs b/crates/hir/src/term_search.rs index 18e6369bfa..00c3e4e31b 100644 --- a/crates/hir/src/term_search.rs +++ b/crates/hir/src/term_search.rs @@ -1,7 +1,5 @@ //! Term search -use std::time::{Duration, Instant}; - use hir_def::type_ref::Mutability; use hir_ty::db::HirDatabase; use itertools::Itertools; @@ -271,20 +269,13 @@ pub struct TermSearchConfig { pub enable_borrowcheck: bool, /// Indicate when to squash multiple trees to `Many` as there are too many to keep track pub many_alternatives_threshold: usize, - /// Depth of the search i.e. number of cycles to run - pub depth: usize, - /// Time fuel for term search - pub fuel: Option, + /// Fuel for term search + pub fuel: u64, } impl Default for TermSearchConfig { fn default() -> Self { - Self { - enable_borrowcheck: true, - many_alternatives_threshold: 1, - depth: 5, - fuel: Some(Duration::from_millis(100)), - } + Self { enable_borrowcheck: true, many_alternatives_threshold: 1, fuel: 400 } } } @@ -319,40 +310,31 @@ pub fn term_search(ctx: &TermSearchCtx<'_, DB>) -> Vec { }); let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold, ctx.goal.clone()); - let start = Instant::now(); + let fuel = std::cell::Cell::new(ctx.config.fuel); + + let should_continue = &|| { + let remaining = fuel.get(); + fuel.set(remaining.saturating_sub(1)); + if remaining == 0 { + tracing::debug!("fuel exhausted"); + } + remaining > 0 + }; // Try trivial tactic first, also populates lookup table let mut solutions: Vec = tactics::trivial(ctx, &defs, &mut lookup).collect(); // Use well known types tactic before iterations as it does not depend on other tactics solutions.extend(tactics::famous_types(ctx, &defs, &mut lookup)); - for _ in 0..ctx.config.depth { + while should_continue() { lookup.new_round(); - if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { - break; - } - solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup)); - if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { - break; - } - solutions.extend(tactics::free_function(ctx, &defs, &mut lookup)); - if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { - break; - } - solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup)); - if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { - break; - } - solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup)); - if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { - break; - } - solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup)); - if ctx.config.fuel.is_some_and(|timeout| start.elapsed() > timeout) { - break; - } - solutions.extend(tactics::make_tuple(ctx, &defs, &mut lookup)); + solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup, should_continue)); + solutions.extend(tactics::free_function(ctx, &defs, &mut lookup, should_continue)); + solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup, should_continue)); + solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup, should_continue)); + solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup, should_continue)); + solutions.extend(tactics::make_tuple(ctx, &defs, &mut lookup, should_continue)); // Discard not interesting `ScopeDef`s for speedup for def in lookup.exhausted_scopedefs() { diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index 63b2a2506f..437e653d88 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -101,12 +101,14 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, lookup: &'a mut LookupTable, + should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); fn variant_helper( db: &dyn HirDatabase, lookup: &mut LookupTable, + should_continue: &dyn std::ops::Fn() -> bool, parent_enum: Enum, variant: Variant, config: &TermSearchConfig, @@ -152,6 +154,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( .chain((non_default_type_params_len == 0).then_some(Vec::new())); generic_params + .filter(|_| should_continue()) .filter_map(move |generics| { // Insert default type params let mut g = generics.into_iter(); @@ -194,8 +197,14 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( defs.iter() .filter_map(move |def| match def { ScopeDef::ModuleDef(ModuleDef::Variant(it)) => { - let variant_exprs = - variant_helper(db, lookup, it.parent_enum(db), *it, &ctx.config); + let variant_exprs = variant_helper( + db, + lookup, + should_continue, + it.parent_enum(db), + *it, + &ctx.config, + ); if variant_exprs.is_empty() { return None; } @@ -213,7 +222,9 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( let exprs: Vec<(Type, Vec)> = enum_ .variants(db) .into_iter() - .flat_map(|it| variant_helper(db, lookup, *enum_, it, &ctx.config)) + .flat_map(|it| { + variant_helper(db, lookup, should_continue, *enum_, it, &ctx.config) + }) .collect(); if exprs.is_empty() { @@ -271,6 +282,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( .chain((non_default_type_params_len == 0).then_some(Vec::new())); let exprs = generic_params + .filter(|_| should_continue()) .filter_map(|generics| { // Insert default type params let mut g = generics.into_iter(); @@ -349,6 +361,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, lookup: &'a mut LookupTable, + should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); @@ -390,6 +403,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>( .permutations(non_default_type_params_len); let exprs: Vec<_> = generic_params + .filter(|_| should_continue()) .filter_map(|generics| { // Insert default type params let mut g = generics.into_iter(); @@ -478,6 +492,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, + should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); @@ -554,6 +569,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( .permutations(non_default_fn_type_params_len); let exprs: Vec<_> = generic_params + .filter(|_| should_continue()) .filter_map(|generics| { // Insert default type params let mut g = generics.into_iter(); @@ -649,6 +665,7 @@ pub(super) fn struct_projection<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, + should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); @@ -656,6 +673,7 @@ pub(super) fn struct_projection<'a, DB: HirDatabase>( .new_types(NewTypesKey::StructProjection) .into_iter() .map(|ty| (ty.clone(), lookup.find(db, &ty).expect("Expr not in lookup"))) + .filter(|_| should_continue()) .flat_map(move |(ty, targets)| { ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| { if !field.is_visible_from(db, module) { @@ -720,6 +738,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, + should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); @@ -728,6 +747,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( .clone() .into_iter() .chain(iter::once(ctx.goal.clone())) + .filter(|_| should_continue()) .flat_map(|ty| { Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp)) }) @@ -801,6 +821,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( .permutations(non_default_fn_type_params_len); let exprs: Vec<_> = generic_params + .filter(|_| should_continue()) .filter_map(|generics| { // Insert default type params let mut g = generics.into_iter(); @@ -888,6 +909,7 @@ pub(super) fn make_tuple<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, lookup: &'a mut LookupTable, + should_continue: &'a dyn std::ops::Fn() -> bool, ) -> impl Iterator + 'a { let db = ctx.sema.db; let module = ctx.scope.module(); @@ -896,6 +918,7 @@ pub(super) fn make_tuple<'a, DB: HirDatabase>( .types_wishlist() .clone() .into_iter() + .filter(|_| should_continue()) .filter(|ty| ty.is_tuple()) .filter_map(move |ty| { // Double check to not contain unknown @@ -915,6 +938,7 @@ pub(super) fn make_tuple<'a, DB: HirDatabase>( let exprs: Vec = param_exprs .into_iter() .multi_cartesian_product() + .filter(|_| should_continue()) .map(|params| { let tys: Vec = params.iter().map(|it| it.ty(db)).collect(); let tuple_ty = Type::new_tuple(module.krate().into(), &tys); diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index e91cbc867b..5ff184ef71 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -353,7 +353,7 @@ pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) config: hir::term_search::TermSearchConfig { enable_borrowcheck: false, many_alternatives_threshold: 1, - depth: 3, + fuel: 200, ..Default::default() }, }; From ab18604309b0a1535a7cdf8c83f15cc7fe1d25c5 Mon Sep 17 00:00:00 2001 From: Tavo Annus Date: Wed, 8 May 2024 18:03:38 +0300 Subject: [PATCH 4/4] Make term search fuel configurable --- crates/hir/src/term_search.rs | 2 +- crates/hir/src/term_search/expr.rs | 4 +--- crates/hir/src/term_search/tactics.rs | 7 +++++++ crates/ide-assists/src/assist_config.rs | 1 + crates/ide-assists/src/handlers/term_search.rs | 4 ++-- crates/ide-assists/src/tests.rs | 3 +++ crates/ide-completion/src/completions/expr.rs | 1 - crates/ide-completion/src/config.rs | 1 + crates/ide-completion/src/tests.rs | 1 + crates/ide-diagnostics/src/handlers/typed_hole.rs | 4 ++-- crates/ide-diagnostics/src/lib.rs | 2 ++ crates/rust-analyzer/src/cli/analysis_stats.rs | 1 + crates/rust-analyzer/src/config.rs | 8 ++++++++ crates/rust-analyzer/src/integrated_benchmarks.rs | 4 ++++ docs/user/generated_config.adoc | 10 ++++++++++ editors/code/package.json | 12 ++++++++++++ 16 files changed, 56 insertions(+), 9 deletions(-) diff --git a/crates/hir/src/term_search.rs b/crates/hir/src/term_search.rs index 00c3e4e31b..5c5ddae19e 100644 --- a/crates/hir/src/term_search.rs +++ b/crates/hir/src/term_search.rs @@ -269,7 +269,7 @@ pub struct TermSearchConfig { pub enable_borrowcheck: bool, /// Indicate when to squash multiple trees to `Many` as there are too many to keep track pub many_alternatives_threshold: usize, - /// Fuel for term search + /// Fuel for term search in "units of work" pub fuel: u64, } diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs index 06372355c5..9f56a1ee55 100644 --- a/crates/hir/src/term_search/expr.rs +++ b/crates/hir/src/term_search/expr.rs @@ -474,9 +474,7 @@ impl Expr { Expr::Method { target, func, .. } => { match func.as_assoc_item(db).and_then(|it| it.container_or_implemented_trait(db)) { Some(_) => false, - None => { - target.is_many() - } + None => target.is_many(), } } Expr::Field { expr, .. } => expr.contains_many_in_illegal_pos(db), diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index 437e653d88..a26728272d 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -4,6 +4,7 @@ //! * `ctx` - Context for the term search //! * `defs` - Set of items in scope at term search target location //! * `lookup` - Lookup table for types +//! * `should_continue` - Function that indicates when to stop iterating //! And they return iterator that yields type trees that unify with the `goal` type. use std::iter; @@ -97,6 +98,7 @@ pub(super) fn trivial<'a, DB: HirDatabase>( /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types +/// * `should_continue` - Function that indicates when to stop iterating pub(super) fn type_constructor<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, @@ -357,6 +359,7 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>( /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types +/// * `should_continue` - Function that indicates when to stop iterating pub(super) fn free_function<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, defs: &'a FxHashSet, @@ -488,6 +491,7 @@ pub(super) fn free_function<'a, DB: HirDatabase>( /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types +/// * `should_continue` - Function that indicates when to stop iterating pub(super) fn impl_method<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, @@ -661,6 +665,7 @@ pub(super) fn impl_method<'a, DB: HirDatabase>( /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types +/// * `should_continue` - Function that indicates when to stop iterating pub(super) fn struct_projection<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, @@ -734,6 +739,7 @@ pub(super) fn famous_types<'a, DB: HirDatabase>( /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types +/// * `should_continue` - Function that indicates when to stop iterating pub(super) fn impl_static_method<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, @@ -905,6 +911,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( /// * `ctx` - Context for the term search /// * `defs` - Set of items in scope at term search target location /// * `lookup` - Lookup table for types +/// * `should_continue` - Function that indicates when to stop iterating pub(super) fn make_tuple<'a, DB: HirDatabase>( ctx: &'a TermSearchCtx<'a, DB>, _defs: &'a FxHashSet, diff --git a/crates/ide-assists/src/assist_config.rs b/crates/ide-assists/src/assist_config.rs index fbe17dbfd7..5d76cb0432 100644 --- a/crates/ide-assists/src/assist_config.rs +++ b/crates/ide-assists/src/assist_config.rs @@ -16,4 +16,5 @@ pub struct AssistConfig { pub prefer_no_std: bool, pub prefer_prelude: bool, pub assist_emit_must_use: bool, + pub term_search_fuel: u64, } diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 0f4a8e3aec..d0c6ae2198 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -1,5 +1,5 @@ //! Term search assist -use hir::term_search::TermSearchCtx; +use hir::term_search::{TermSearchConfig, TermSearchCtx}; use ide_db::{ assists::{AssistId, AssistKind, GroupLabel}, famous_defs::FamousDefs, @@ -34,7 +34,7 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< sema: &ctx.sema, scope: &scope, goal: target_ty, - config: Default::default(), + config: TermSearchConfig { fuel: ctx.config.term_search_fuel, ..Default::default() }, }; let paths = hir::term_search::term_search(&term_search_ctx); diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index 32d6984102..3b6c951251 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -31,6 +31,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { prefer_no_std: false, prefer_prelude: true, assist_emit_must_use: false, + term_search_fuel: 400, }; pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { @@ -46,6 +47,7 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { prefer_no_std: false, prefer_prelude: true, assist_emit_must_use: false, + term_search_fuel: 400, }; pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig { @@ -61,6 +63,7 @@ pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig { prefer_no_std: false, prefer_prelude: true, assist_emit_must_use: false, + term_search_fuel: 400, }; pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 5ff184ef71..1e31d65fdd 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -354,7 +354,6 @@ pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) enable_borrowcheck: false, many_alternatives_threshold: 1, fuel: 200, - ..Default::default() }, }; let exprs = hir::term_search::term_search(&term_search_ctx); diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs index 04563fb0f4..809c305ed8 100644 --- a/crates/ide-completion/src/config.rs +++ b/crates/ide-completion/src/config.rs @@ -15,6 +15,7 @@ pub struct CompletionConfig { pub enable_self_on_the_fly: bool, pub enable_private_editable: bool, pub enable_term_search: bool, + pub term_search_fuel: u64, pub full_function_signatures: bool, pub callable: Option, pub snippet_cap: Option, diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 1f032c7df4..70e0aa4e9a 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -80,6 +80,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { }, snippets: Vec::new(), limit: None, + term_search_fuel: 200, }; pub(crate) fn completion_list(ra_fixture: &str) -> String { diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index e78bd2db8b..656d79dc73 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -1,6 +1,6 @@ use hir::{ db::ExpandDatabase, - term_search::{term_search, TermSearchCtx}, + term_search::{term_search, TermSearchConfig, TermSearchCtx}, ClosureStyle, HirDisplay, }; use ide_db::{ @@ -47,7 +47,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option sema: &ctx.sema, scope: &scope, goal: d.expected.clone(), - config: Default::default(), + config: TermSearchConfig { fuel: ctx.config.term_search_fuel, ..Default::default() }, }; let paths = term_search(&term_search_ctx); diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index c3ced36a69..135824386a 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -232,6 +232,7 @@ pub struct DiagnosticsConfig { pub insert_use: InsertUseConfig, pub prefer_no_std: bool, pub prefer_prelude: bool, + pub term_search_fuel: u64, } impl DiagnosticsConfig { @@ -256,6 +257,7 @@ impl DiagnosticsConfig { }, prefer_no_std: false, prefer_prelude: true, + term_search_fuel: 400, } } } diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index a1eea8839e..5208aa9bf0 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -986,6 +986,7 @@ impl flags::AnalysisStats { prefer_no_std: false, prefer_prelude: true, style_lints: false, + term_search_fuel: 400, }, ide::AssistResolveStrategy::All, file_id, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e16595c992..6c332ae1cb 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -454,6 +454,9 @@ config_data! { /// Local configurations can be overridden for every crate by placing a `rust-analyzer.toml` on crate root. /// A config is searched for by traversing a "config tree" in a bottom up fashion. It is chosen by the nearest first principle. local: struct LocalDefaultConfigData <- LocalConfigInput -> { + /// Term search fuel in "units of work" for assists (Defaults to 400). + assist_termSearch_fuel: usize = 400, + /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. completion_autoimport_enable: bool = true, @@ -515,6 +518,8 @@ config_data! { }"#).unwrap(), /// Whether to enable term search based snippets like `Some(foo.bar().baz())`. completion_termSearch_enable: bool = false, + /// Term search fuel in "units of work" for autocompletion (Defaults to 200). + completion_termSearch_fuel: usize = 200, /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. highlightRelated_breakPoints_enable: bool = true, @@ -1015,6 +1020,7 @@ impl Config { prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), assist_emit_must_use: self.assist_emitMustUse().to_owned(), prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), + term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64, } } @@ -1048,6 +1054,7 @@ impl Config { snippets: self.snippets.clone().to_vec(), limit: self.completion_limit(source_root).to_owned(), enable_term_search: self.completion_termSearch_enable(source_root).to_owned(), + term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64, prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), } } @@ -1067,6 +1074,7 @@ impl Config { prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), style_lints: self.diagnostics_styleLints_enable().to_owned(), + term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64, } } pub fn expand_proc_attr_macros(&self) -> bool { diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index 7b385ca9d9..cc83d6246b 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -153,6 +153,7 @@ fn integrated_completion_benchmark() { prefer_no_std: false, prefer_prelude: true, limit: None, + term_search_fuel: 200, }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -197,6 +198,7 @@ fn integrated_completion_benchmark() { prefer_no_std: false, prefer_prelude: true, limit: None, + term_search_fuel: 200, }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -239,6 +241,7 @@ fn integrated_completion_benchmark() { prefer_no_std: false, prefer_prelude: true, limit: None, + term_search_fuel: 200, }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -295,6 +298,7 @@ fn integrated_diagnostics_benchmark() { }, prefer_no_std: false, prefer_prelude: false, + term_search_fuel: 400, }; host.analysis() .diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id) diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index e9d60063c6..8993a46d2b 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -9,6 +9,11 @@ for enum variants. -- Placeholder expression to use for missing expressions in assists. -- +[[rust-analyzer.assist.termSearch.fuel]]rust-analyzer.assist.termSearch.fuel (default: `400`):: ++ +-- +Term search fuel in "units of work" for assists (Defaults to 400). +-- [[rust-analyzer.cachePriming.enable]]rust-analyzer.cachePriming.enable (default: `true`):: + -- @@ -373,6 +378,11 @@ Custom completion snippets. -- Whether to enable term search based snippets like `Some(foo.bar().baz())`. -- +[[rust-analyzer.completion.termSearch.fuel]]rust-analyzer.completion.termSearch.fuel (default: `200`):: ++ +-- +Term search fuel in "units of work" for autocompletion (Defaults to 200). +-- [[rust-analyzer.diagnostics.disabled]]rust-analyzer.diagnostics.disabled (default: `[]`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index c4bbb7932e..6e4fedd992 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -534,6 +534,12 @@ "Fill missing expressions with reasonable defaults, `new` or `default` constructors." ] }, + "rust-analyzer.assist.termSearch.fuel": { + "markdownDescription": "Term search fuel in \"units of work\" for assists (Defaults to 400).", + "default": 400, + "type": "integer", + "minimum": 0 + }, "rust-analyzer.cachePriming.enable": { "markdownDescription": "Warm up caches on project load.", "default": true, @@ -930,6 +936,12 @@ "default": false, "type": "boolean" }, + "rust-analyzer.completion.termSearch.fuel": { + "markdownDescription": "Term search fuel in \"units of work\" for autocompletion (Defaults to 200).", + "default": 200, + "type": "integer", + "minimum": 0 + }, "rust-analyzer.diagnostics.disabled": { "markdownDescription": "List of rust-analyzer diagnostics to disable.", "default": [],