diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs index b43f10528d..29590a0730 100644 --- a/crates/hir/src/term_search/expr.rs +++ b/crates/hir/src/term_search/expr.rs @@ -11,7 +11,12 @@ use crate::{ }; /// Helper function to get path to `ModuleDef` -fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option { +fn mod_item_path( + sema_scope: &SemanticsScope<'_>, + def: &ModuleDef, + prefer_no_std: bool, + prefer_prelude: bool, +) -> Option { let db = sema_scope.db; // Account for locals shadowing items from module let name_hit_count = def.name(db).map(|def_name| { @@ -26,25 +31,43 @@ fn mod_item_path(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> Option m.find_use_path(db.upcast(), *def, false, true), - Some(_) => m.find_use_path_prefixed(db.upcast(), *def, PrefixKind::ByCrate, false, true), + Some(0..=1) | None => m.find_use_path(db.upcast(), *def, prefer_no_std, prefer_prelude), + Some(_) => m.find_use_path_prefixed( + db.upcast(), + *def, + PrefixKind::ByCrate, + prefer_no_std, + prefer_prelude, + ), } } /// Helper function to get path to `ModuleDef` as string -fn mod_item_path_str(sema_scope: &SemanticsScope<'_>, def: &ModuleDef) -> String { - let path = mod_item_path(sema_scope, def); +fn mod_item_path_str( + sema_scope: &SemanticsScope<'_>, + def: &ModuleDef, + prefer_no_std: bool, + prefer_prelude: bool, +) -> String { + let path = mod_item_path(sema_scope, def, prefer_no_std, prefer_prelude); path.map(|it| it.display(sema_scope.db.upcast()).to_string()).unwrap() } /// Helper function to get path to `Type` -fn type_path(sema_scope: &SemanticsScope<'_>, ty: &Type) -> String { +fn type_path( + sema_scope: &SemanticsScope<'_>, + ty: &Type, + prefer_no_std: bool, + prefer_prelude: bool, +) -> String { let db = sema_scope.db; match ty.as_adt() { Some(adt) => { let ty_name = ty.display(db).to_string(); - let mut path = mod_item_path(sema_scope, &ModuleDef::Adt(adt)).unwrap(); + let mut path = + mod_item_path(sema_scope, &ModuleDef::Adt(adt), prefer_no_std, prefer_prelude) + .unwrap(); path.pop_segment(); let path = path.display(db.upcast()).to_string(); match path.is_empty() { @@ -125,8 +148,11 @@ impl Expr { &self, sema_scope: &SemanticsScope<'_>, many_formatter: &mut dyn FnMut(&Type) -> String, + prefer_no_std: bool, + prefer_prelude: bool, ) -> String { let db = sema_scope.db; + let mod_item_path_str = |s, def| mod_item_path_str(s, def, prefer_no_std, prefer_prelude); match self { Expr::Const(it) => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)), Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)), @@ -134,8 +160,12 @@ impl Expr { Expr::ConstParam(it) => return it.name(db).display(db.upcast()).to_string(), Expr::FamousType { value, .. } => return value.to_string(), Expr::Function { func, params, .. } => { - let args = - params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", "); + let args = params + .iter() + .map(|f| { + f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude) + }) + .join(", "); match func.as_assoc_item(db).map(|it| it.container(db)) { Some(container) => { @@ -146,10 +176,14 @@ impl Expr { crate::AssocItemContainer::Impl(imp) => { let self_ty = imp.self_ty(db); // Should it be guaranteed that `mod_item_path` always exists? - match self_ty - .as_adt() - .and_then(|adt| mod_item_path(sema_scope, &adt.into())) - { + match self_ty.as_adt().and_then(|adt| { + mod_item_path( + sema_scope, + &adt.into(), + prefer_no_std, + prefer_prelude, + ) + }) { Some(path) => path.display(sema_scope.db.upcast()).to_string(), None => self_ty.display(db).to_string(), } @@ -171,9 +205,18 @@ impl Expr { 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(sema_scope, many_formatter); - let args = - params.iter().map(|f| f.gen_source_code(sema_scope, many_formatter)).join(", "); + let target = target.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude, + ); + let args = params + .iter() + .map(|f| { + f.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude) + }) + .join(", "); match func.as_assoc_item(db).and_then(|it| it.containing_trait_or_trait_impl(db)) { Some(trait_) => { @@ -196,8 +239,10 @@ impl Expr { let generics_str = match generics.is_empty() { true => String::new(), false => { - let generics = - generics.iter().map(|it| type_path(sema_scope, it)).join(", "); + let generics = generics + .iter() + .map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude)) + .join(", "); format!("::<{generics}>") } }; @@ -205,7 +250,14 @@ impl Expr { StructKind::Tuple => { let args = params .iter() - .map(|f| f.gen_source_code(sema_scope, many_formatter)) + .map(|f| { + f.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude, + ) + }) .join(", "); format!("{generics_str}({args})") } @@ -218,7 +270,12 @@ impl Expr { format!( "{}: {}", f.name(db).display(db.upcast()).to_string(), - a.gen_source_code(sema_scope, many_formatter) + a.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude + ) ) }) .join(", "); @@ -236,7 +293,14 @@ impl Expr { StructKind::Tuple => { let args = params .iter() - .map(|a| a.gen_source_code(sema_scope, many_formatter)) + .map(|a| { + a.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude, + ) + }) .join(", "); format!("({args})") } @@ -249,7 +313,12 @@ impl Expr { format!( "{}: {}", f.name(db).display(db.upcast()).to_string(), - a.gen_source_code(sema_scope, many_formatter) + a.gen_source_code( + sema_scope, + many_formatter, + prefer_no_std, + prefer_prelude + ) ) }) .join(", "); @@ -258,8 +327,10 @@ impl Expr { StructKind::Unit => match generics.is_empty() { true => String::new(), false => { - let generics = - generics.iter().map(|it| type_path(sema_scope, it)).join(", "); + let generics = generics + .iter() + .map(|it| type_path(sema_scope, it, prefer_no_std, prefer_prelude)) + .join(", "); format!("::<{generics}>") } }, @@ -273,7 +344,8 @@ impl Expr { return many_formatter(&expr.ty(db)); } - let strukt = expr.gen_source_code(sema_scope, many_formatter); + let strukt = + expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude); let field = field.name(db).display(db.upcast()).to_string(); format!("{strukt}.{field}") } @@ -282,7 +354,8 @@ impl Expr { return many_formatter(&expr.ty(db)); } - let inner = expr.gen_source_code(sema_scope, many_formatter); + let inner = + expr.gen_source_code(sema_scope, many_formatter, prefer_no_std, prefer_prelude); format!("&{inner}") } Expr::Many(ty) => many_formatter(ty), diff --git a/crates/hir/src/term_search/tactics.rs b/crates/hir/src/term_search/tactics.rs index a8282359ce..012d815394 100644 --- a/crates/hir/src/term_search/tactics.rs +++ b/crates/hir/src/term_search/tactics.rs @@ -760,7 +760,7 @@ pub(super) fn impl_static_method<'a, DB: HirDatabase>( .count(); // Ignore bigger number of generics for now as they kill the performance - if non_default_type_params_len > 0 { + if non_default_type_params_len > 1 { return None; } diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 89a7bb974a..6b054790e9 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -38,7 +38,12 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let mut formatter = |_: &hir::Type| String::from("todo!()"); for path in paths.iter().unique() { - let code = path.gen_source_code(&scope, &mut formatter); + let code = path.gen_source_code( + &scope, + &mut formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ); acc.add_group( &GroupLabel(String::from("Term search")), AssistId("term_search", AssistKind::Generate), diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 9708f9c526..c37b325ee7 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -331,6 +331,11 @@ pub(crate) fn complete_expr_path( pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) { let _p = tracing::span!(tracing::Level::INFO, "complete_expr").entered(); + + if !ctx.config.enable_term_search { + return; + } + if !ctx.qualifier_ctx.none() { return; } diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs index ed5ddde8fb..04563fb0f4 100644 --- a/crates/ide-completion/src/config.rs +++ b/crates/ide-completion/src/config.rs @@ -14,6 +14,7 @@ pub struct CompletionConfig { pub enable_imports_on_the_fly: bool, pub enable_self_on_the_fly: bool, pub enable_private_editable: bool, + pub enable_term_search: bool, pub full_function_signatures: bool, pub callable: Option, pub snippet_cap: Option, diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 6b102257ed..1bac2e30c1 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -295,7 +295,12 @@ pub(crate) fn render_expr( .unwrap_or_else(|| String::from("...")) }; - let label = expr.gen_source_code(&ctx.scope, &mut label_formatter); + let label = expr.gen_source_code( + &ctx.scope, + &mut label_formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ); let source_range = match ctx.original_token.parent() { Some(node) => match node.ancestors().find_map(|n| ast::Path::cast(n)) { @@ -307,7 +312,15 @@ pub(crate) fn render_expr( let mut item = CompletionItem::new(CompletionItemKind::Snippet, source_range, label.clone()); - let snippet = format!("{}$0", expr.gen_source_code(&ctx.scope, &mut snippet_formatter)); + let snippet = format!( + "{}$0", + expr.gen_source_code( + &ctx.scope, + &mut snippet_formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude + ) + ); let edit = TextEdit::replace(source_range, snippet); item.snippet_edit(ctx.config.snippet_cap?, edit); item.documentation(Documentation::new(String::from("Autogenerated expression by term search"))); diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 154b69875a..1f032c7df4 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -65,6 +65,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: false, + enable_term_search: true, full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs index 035bc75adb..7aeadce8e2 100644 --- a/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -1,13 +1,12 @@ use hir::{ db::ExpandDatabase, term_search::{term_search, TermSearchCtx}, - ClosureStyle, HirDisplay, Semantics, + ClosureStyle, HirDisplay, }; use ide_db::{ assists::{Assist, AssistId, AssistKind, GroupLabel}, label::Label, source_change::SourceChange, - RootDatabase, }; use itertools::Itertools; use text_edit::TextEdit; @@ -29,7 +28,7 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di "invalid `_` expression, expected type `{}`", d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), ), - fixes(&ctx.sema, d), + fixes(ctx, d), ) }; @@ -37,21 +36,30 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di .with_fixes(fixes) } -fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::TypedHole) -> Option> { - let db = sema.db; +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option> { + let db = ctx.sema.db; let root = db.parse_or_expand(d.expr.file_id); let (original_range, _) = d.expr.as_ref().map(|it| it.to_node(&root)).syntax().original_file_range_opt(db)?; - let scope = sema.scope(d.expr.value.to_node(&root).syntax())?; + let scope = ctx.sema.scope(d.expr.value.to_node(&root).syntax())?; - let ctx = - TermSearchCtx { sema, scope: &scope, goal: d.expected.clone(), config: Default::default() }; - let paths = term_search(&ctx); + let term_search_ctx = TermSearchCtx { + sema: &ctx.sema, + scope: &scope, + goal: d.expected.clone(), + config: Default::default(), + }; + let paths = term_search(&term_search_ctx); let mut assists = vec![]; let mut formatter = |_: &hir::Type| String::from("_"); for path in paths.into_iter().unique() { - let code = path.gen_source_code(&scope, &mut formatter); + let code = path.gen_source_code( + &scope, + &mut formatter, + ctx.config.prefer_no_std, + ctx.config.prefer_prelude, + ); assists.push(Assist { id: AssistId("typed-hole", AssistKind::QuickFix), diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 9d1ef728a4..efad2ff6d1 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -432,7 +432,7 @@ impl flags::AnalysisStats { let mut formatter = |_: &hir::Type| todo.clone(); let mut syntax_hit_found = false; for term in found_terms { - let generated = term.gen_source_code(&scope, &mut formatter); + let generated = term.gen_source_code(&scope, &mut formatter, false, true); syntax_hit_found |= trim(&original_text) == trim(&generated); // Validate if type-checks diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 7bdd9ec866..298d92468f 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -286,6 +286,8 @@ config_data! { "scope": "expr" } }"#, + /// Whether to enable term search based snippets like `Some(foo.bar().baz())`. + completion_term_search_enable: bool = "true", /// List of rust-analyzer diagnostics to disable. diagnostics_disabled: FxHashSet = "[]", @@ -1535,6 +1537,7 @@ impl Config { && completion_item_edit_resolve(&self.caps), enable_self_on_the_fly: self.data.completion_autoself_enable, enable_private_editable: self.data.completion_privateEditable_enable, + enable_term_search: self.data.completion_term_search_enable, full_function_signatures: self.data.completion_fullFunctionSignatures_enable, callable: match self.data.completion_callable_snippets { CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments), diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index acc02d6447..f0eee77aff 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -132,6 +132,7 @@ fn integrated_completion_benchmark() { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: true, + enable_term_search: true, full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), @@ -175,6 +176,7 @@ fn integrated_completion_benchmark() { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: true, + enable_term_search: true, full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), @@ -216,6 +218,7 @@ fn integrated_completion_benchmark() { enable_imports_on_the_fly: true, enable_self_on_the_fly: true, enable_private_editable: true, + enable_term_search: true, full_function_signatures: false, callable: Some(CallableSnippets::FillArguments), snippet_cap: SnippetCap::new(true), diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index a86ef70941..9d54999fa3 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -343,6 +343,11 @@ Default: ---- Custom completion snippets. +-- +[[rust-analyzer.completion.term.search.enable]]rust-analyzer.completion.term.search.enable (default: `true`):: ++ +-- +Whether to enable term search based snippets like `Some(foo.bar().baz())`. -- [[rust-analyzer.diagnostics.disabled]]rust-analyzer.diagnostics.disabled (default: `[]`):: + diff --git a/editors/code/package.json b/editors/code/package.json index b474471e5a..3352007253 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -902,6 +902,11 @@ }, "type": "object" }, + "rust-analyzer.completion.term.search.enable": { + "markdownDescription": "Whether to enable term search based snippets like `Some(foo.bar().baz())`.", + "default": true, + "type": "boolean" + }, "rust-analyzer.diagnostics.disabled": { "markdownDescription": "List of rust-analyzer diagnostics to disable.", "default": [],