Repository: rust-lang/rust-analyzer
Pull request #17516 (2024-06-30 09:52:43)

Quality of life improvements to term search
Basically two things:
- Allow optionally disabling "borrow checking" restrictions on term search code assists. Sometimes it is better to get invalid suggestions and fix borrow checking issues later...
- Remove explicit generics in generated expressions. I find it quite rare that one writes `None::<T>` instead of `None`.


Patch:
diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs
index a6faa97196..0c8f6932c7 100644
--- a/crates/hir/src/term_search/expr.rs
+++ b/crates/hir/src/term_search/expr.rs
@@ -9,8 +9,8 @@ use hir_ty::{
 use itertools::Itertools;
 
 use crate::{
-    Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Field, Function, GenericDef, Local,
-    ModuleDef, SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant,
+    Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Field, Function, Local, ModuleDef,
+    SemanticsScope, Static, Struct, StructKind, Trait, Type, Variant,
 };
 
 /// Helper function to get path to `ModuleDef`
@@ -35,43 +35,6 @@ fn mod_item_path_str(
         .ok_or(DisplaySourceCodeError::PathNotFound)
 }
 
-/// Helper function to get path to `Type`
-fn type_path(
-    sema_scope: &SemanticsScope<'_>,
-    ty: &Type,
-    cfg: ImportPathConfig,
-) -> Result<String, DisplaySourceCodeError> {
-    let db = sema_scope.db;
-    let m = sema_scope.module();
-
-    match ty.as_adt() {
-        Some(adt) => {
-            let ty_name = ty.display_source_code(db, m.id, true)?;
-
-            let mut path = mod_item_path(sema_scope, &ModuleDef::Adt(adt), cfg).unwrap();
-            path.pop_segment();
-            let path = path.display(db.upcast()).to_string();
-            let res = match path.is_empty() {
-                true => ty_name,
-                false => format!("{path}::{ty_name}"),
-            };
-            Ok(res)
-        }
-        None => ty.display_source_code(db, m.id, true),
-    }
-}
-
-/// Helper function to filter out generic parameters that are default
-fn non_default_generics(db: &dyn HirDatabase, def: GenericDef, generics: &[Type]) -> Vec<Type> {
-    def.type_or_const_params(db)
-        .into_iter()
-        .filter_map(|it| it.as_type_param(db))
-        .zip(generics)
-        .filter(|(tp, arg)| tp.default(db).as_ref() != Some(arg))
-        .map(|(_, arg)| arg.clone())
-        .collect()
-}
-
 /// Type tree shows how can we get from set of types to some type.
 ///
 /// Consider the following code as an example
@@ -208,20 +171,7 @@ impl Expr {
                     None => Ok(format!("{target_str}.{func_name}({args})")),
                 }
             }
-            Expr::Variant { variant, generics, params } => {
-                let generics = non_default_generics(db, variant.parent_enum(db).into(), generics);
-                let generics_str = match generics.is_empty() {
-                    true => String::new(),
-                    false => {
-                        let generics = generics
-                            .iter()
-                            .map(|it| type_path(sema_scope, it, cfg))
-                            .collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
-                            .into_iter()
-                            .join(", ");
-                        format!("::<{generics}>")
-                    }
-                };
+            Expr::Variant { variant, params, .. } => {
                 let inner = match variant.kind(db) {
                     StructKind::Tuple => {
                         let args = params
@@ -230,7 +180,7 @@ impl Expr {
                             .collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
                             .into_iter()
                             .join(", ");
-                        format!("{generics_str}({args})")
+                        format!("({args})")
                     }
                     StructKind::Record => {
                         let fields = variant.fields(db);
@@ -248,16 +198,15 @@ impl Expr {
                             .collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
                             .into_iter()
                             .join(", ");
-                        format!("{generics_str}{{ {args} }}")
+                        format!("{{ {args} }}")
                     }
-                    StructKind::Unit => generics_str,
+                    StructKind::Unit => String::new(),
                 };
 
                 let prefix = mod_item_path_str(sema_scope, &ModuleDef::Variant(*variant))?;
                 Ok(format!("{prefix}{inner}"))
             }
-            Expr::Struct { strukt, generics, params } => {
-                let generics = non_default_generics(db, (*strukt).into(), generics);
+            Expr::Struct { strukt, params, .. } => {
                 let inner = match strukt.kind(db) {
                     StructKind::Tuple => {
                         let args = params
@@ -286,18 +235,7 @@ impl Expr {
                             .join(", ");
                         format!(" {{ {args} }}")
                     }
-                    StructKind::Unit => match generics.is_empty() {
-                        true => String::new(),
-                        false => {
-                            let generics = generics
-                                .iter()
-                                .map(|it| type_path(sema_scope, it, cfg))
-                                .collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
-                                .into_iter()
-                                .join(", ");
-                            format!("::<{generics}>")
-                        }
-                    },
+                    StructKind::Unit => String::new(),
                 };
 
                 let prefix = mod_item_path_str(sema_scope, &ModuleDef::Adt(Adt::Struct(*strukt)))?;
diff --git a/crates/ide-assists/src/assist_config.rs b/crates/ide-assists/src/assist_config.rs
index 5d76cb0432..410237f9ca 100644
--- a/crates/ide-assists/src/assist_config.rs
+++ b/crates/ide-assists/src/assist_config.rs
@@ -17,4 +17,5 @@ pub struct AssistConfig {
     pub prefer_prelude: bool,
     pub assist_emit_must_use: bool,
     pub term_search_fuel: u64,
+    pub term_search_borrowck: bool,
 }
diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs
index b62c3b3323..874b42f51b 100644
--- a/crates/ide-assists/src/handlers/term_search.rs
+++ b/crates/ide-assists/src/handlers/term_search.rs
@@ -37,7 +37,11 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
         sema: &ctx.sema,
         scope: &scope,
         goal: target_ty,
-        config: TermSearchConfig { fuel: ctx.config.term_search_fuel, ..Default::default() },
+        config: TermSearchConfig {
+            fuel: ctx.config.term_search_fuel,
+            enable_borrowcheck: ctx.config.term_search_borrowck,
+            ..Default::default()
+        },
     };
     let paths = hir::term_search::term_search(&term_search_ctx);
 
@@ -144,7 +148,7 @@ fn f() { let a = A { x: 1, y: true }; let b: i32 = a.x; }"#,
             term_search,
             r#"//- minicore: todo, unimplemented, option
 fn f() { let a: i32 = 1; let b: Option<i32> = todo$0!(); }"#,
-            r#"fn f() { let a: i32 = 1; let b: Option<i32> = Some::<i32>(a); }"#,
+            r#"fn f() { let a: i32 = 1; let b: Option<i32> = Some(a); }"#,
         )
     }
 
@@ -156,7 +160,7 @@ fn f() { let a: i32 = 1; let b: Option<i32> = todo$0!(); }"#,
 enum Option<T> { None, Some(T) }
 fn f() { let a: i32 = 1; let b: Option<i32> = todo$0!(); }"#,
             r#"enum Option<T> { None, Some(T) }
-fn f() { let a: i32 = 1; let b: Option<i32> = Option::Some::<i32>(a); }"#,
+fn f() { let a: i32 = 1; let b: Option<i32> = Option::Some(a); }"#,
         )
     }
 
@@ -168,7 +172,7 @@ fn f() { let a: i32 = 1; let b: Option<i32> = Option::Some::<i32>(a); }"#,
 enum Option<T> { None, Some(T) }
 fn f() { let a: Option<i32> = Option::None; let b: Option<Option<i32>> = todo$0!(); }"#,
             r#"enum Option<T> { None, Some(T) }
-fn f() { let a: Option<i32> = Option::None; let b: Option<Option<i32>> = Option::Some::<Option<i32>>(a); }"#,
+fn f() { let a: Option<i32> = Option::None; let b: Option<Option<i32>> = Option::Some(a); }"#,
         )
     }
 
@@ -180,7 +184,7 @@ fn f() { let a: Option<i32> = Option::None; let b: Option<Option<i32>> = Option:
 enum Foo<T = i32> { Foo(T) }
 fn f() { let a = 0; let b: Foo = todo$0!(); }"#,
             r#"enum Foo<T = i32> { Foo(T) }
-fn f() { let a = 0; let b: Foo = Foo::Foo::<i32>(a); }"#,
+fn f() { let a = 0; let b: Foo = Foo::Foo(a); }"#,
         );
 
         check_assist(
diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs
index 1883377408..02298b1e9b 100644
--- a/crates/ide-completion/src/completions/item_list.rs
+++ b/crates/ide-completion/src/completions/item_list.rs
@@ -82,30 +82,17 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option
     let no_vis_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
     let in_block = kind.is_none();
 
-    let missing_qualifiers = [
-        ctx.qualifier_ctx.unsafe_tok.is_none().then_some(("unsafe", "unsafe $0")),
-        ctx.qualifier_ctx.async_tok.is_none().then_some(("async", "async $0")),
-    ];
-
     if !in_trait_impl {
-        // handle qualifier tokens
-        if missing_qualifiers.iter().any(Option::is_none) {
-            // only complete missing qualifiers
-            missing_qualifiers.iter().filter_map(|x| *x).for_each(|(kw, snippet)| {
-                add_keyword(kw, snippet);
-            });
-
+        if ctx.qualifier_ctx.unsafe_tok.is_some() {
             if in_item_list || in_assoc_non_trait_impl {
                 add_keyword("fn", "fn $1($2) {\n    $0\n}");
             }
-
-            if ctx.qualifier_ctx.unsafe_tok.is_some() && in_item_list {
+            if in_item_list {
                 add_keyword("trait", "trait $1 {\n    $0\n}");
                 if no_vis_qualifiers {
                     add_keyword("impl", "impl $1 {\n    $0\n}");
                 }
             }
-
             return;
         }
 
@@ -113,6 +100,7 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option
             add_keyword("enum", "enum $1 {\n    $0\n}");
             add_keyword("mod", "mod $0");
             add_keyword("static", "static $0");
+            add_keyword("async", "async $0");
             add_keyword("struct", "struct $0");
             add_keyword("trait", "trait $1 {\n    $0\n}");
             add_keyword("union", "union $1 {\n    $0\n}");
@@ -141,7 +129,6 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option
             add_keyword("fn", "fn $1($2) {\n    $0\n}");
             add_keyword("unsafe", "unsafe $0");
             add_keyword("const", "const $0");
-            add_keyword("async", "async $0");
         }
     }
 }
diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs
index 3f50cd55cb..d79b539882 100644
--- a/crates/ide-completion/src/completions/keyword.rs
+++ b/crates/ide-completion/src/completions/keyword.rs
@@ -57,7 +57,6 @@ mod tests {
         check(
             r"fn my_fn() { unsafe $0 }",
             expect![[r#"
-                kw async
                 kw fn
                 kw impl
                 kw trait
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 5782a4423a..992ca18bb0 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -46,15 +46,13 @@ pub(crate) enum Visible {
 /// Existing qualifiers for the thing we are currently completing.
 #[derive(Debug, Default)]
 pub(crate) struct QualifierCtx {
-    // TODO: Add try_tok and default_tok
-    pub(crate) async_tok: Option<SyntaxToken>,
     pub(crate) unsafe_tok: Option<SyntaxToken>,
     pub(crate) vis_node: Option<ast::Visibility>,
 }
 
 impl QualifierCtx {
     pub(crate) fn none(&self) -> bool {
-        self.async_tok.is_none() && self.unsafe_tok.is_none() && self.vis_node.is_none()
+        self.unsafe_tok.is_none() && self.vis_node.is_none()
     }
 }
 
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 80dcfd2f52..743aa00570 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -1287,15 +1287,10 @@ fn classify_name_ref(
                 syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev)
             {
                 if error_node.kind() == SyntaxKind::ERROR {
-                    for token in
-                        error_node.children_with_tokens().filter_map(NodeOrToken::into_token)
-                    {
-                        match token.kind() {
-                            SyntaxKind::UNSAFE_KW => qualifier_ctx.unsafe_tok = Some(token),
-                            SyntaxKind::ASYNC_KW => qualifier_ctx.async_tok = Some(token),
-                            _ => {}
-                        }
-                    }
+                    qualifier_ctx.unsafe_tok = error_node
+                        .children_with_tokens()
+                        .filter_map(NodeOrToken::into_token)
+                        .find(|it| it.kind() == T![unsafe]);
                     qualifier_ctx.vis_node = error_node.children().find_map(ast::Visibility::cast);
                 }
             }
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index ff60a7669e..b98f745f17 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -2641,7 +2641,7 @@ fn foo() {
             expect![[r#"
                 lc foo [type+local]
                 ex foo [type]
-                ex Foo::B::<u32> [type]
+                ex Foo::B [type]
                 ev Foo::A(…) [type_could_unify]
                 ev Foo::B [type_could_unify]
                 en Foo [type_could_unify]
diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs
index 71917e09f0..a64bac2949 100644
--- a/crates/ide-diagnostics/src/handlers/typed_hole.rs
+++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -47,7 +47,12 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>
         sema: &ctx.sema,
         scope: &scope,
         goal: d.expected.clone(),
-        config: TermSearchConfig { fuel: ctx.config.term_search_fuel, ..Default::default() },
+        config: TermSearchConfig {
+            fuel: ctx.config.term_search_fuel,
+            enable_borrowcheck: ctx.config.term_search_borrowck,
+
+            ..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 a419f04bfa..f176c30c4a 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -234,6 +234,7 @@ pub struct DiagnosticsConfig {
     pub prefer_no_std: bool,
     pub prefer_prelude: bool,
     pub term_search_fuel: u64,
+    pub term_search_borrowck: bool,
 }
 
 impl DiagnosticsConfig {
@@ -260,6 +261,7 @@ impl DiagnosticsConfig {
             prefer_no_std: false,
             prefer_prelude: true,
             term_search_fuel: 400,
+            term_search_borrowck: true,
         }
     }
 }
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 90b81d0a80..a429f75568 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -994,6 +994,7 @@ impl flags::AnalysisStats {
                     prefer_prelude: true,
                     style_lints: false,
                     term_search_fuel: 400,
+                    term_search_borrowck: true,
                 },
                 ide::AssistResolveStrategy::All,
                 file_id,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index f719a47248..86b61629e3 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -341,6 +341,8 @@ config_data! {
         assist_emitMustUse: bool               = false,
         /// Placeholder expression to use for missing expressions in assists.
         assist_expressionFillDefault: ExprFillDefaultDef              = ExprFillDefaultDef::Todo,
+        /// Enable borrow checking for term search code assists. If set to false, also there will be more suggestions, but some of them may not borrow-check.
+        assist_termSearch_borrowcheck: bool = true,
         /// Term search fuel in "units of work" for assists (Defaults to 1800).
         assist_termSearch_fuel: usize = 1800,
 
@@ -1269,6 +1271,7 @@ impl Config {
             assist_emit_must_use: self.assist_emitMustUse(source_root).to_owned(),
             prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
             term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
+            term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(),
         }
     }
 
@@ -1328,6 +1331,7 @@ impl Config {
             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,
+            term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(),
         }
     }
     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 1e2cd4339b..65fbc96dad 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -300,6 +300,7 @@ fn integrated_diagnostics_benchmark() {
         prefer_no_std: false,
         prefer_prelude: false,
         term_search_fuel: 400,
+        term_search_borrowck: true,
     };
     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 81f30dc56e..25d9430621 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.borrowcheck]]rust-analyzer.assist.termSearch.borrowcheck (default: `true`)::
++
+--
+Enable borrow checking for term search code assists. If set to false, also there will be more suggestions, but some of them may not borrow-check.
+--
 [[rust-analyzer.assist.termSearch.fuel]]rust-analyzer.assist.termSearch.fuel (default: `1800`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 0da5b920a7..7f9987b935 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -588,6 +588,16 @@
                     }
                 }
             },
+            {
+                "title": "assist",
+                "properties": {
+                    "rust-analyzer.assist.termSearch.borrowcheck": {
+                        "markdownDescription": "Enable borrow checking for term search code assists. If set to false, also there will be more suggestions, but some of them may not borrow-check.",
+                        "default": true,
+                        "type": "boolean"
+                    }
+                }
+            },
             {
                 "title": "assist",
                 "properties": {


Test patch:
diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs
index 3b6c951251..bd10b5481b 100644
--- a/crates/ide-assists/src/tests.rs
+++ b/crates/ide-assists/src/tests.rs
@@ -32,6 +32,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
     prefer_prelude: true,
     assist_emit_must_use: false,
     term_search_fuel: 400,
+    term_search_borrowck: true,
 };
 
 pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig {
@@ -48,6 +49,7 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig {
     prefer_prelude: true,
     assist_emit_must_use: false,
     term_search_fuel: 400,
+    term_search_borrowck: true,
 };
 
 pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig {
@@ -64,6 +66,7 @@ pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig {
     prefer_prelude: true,
     assist_emit_must_use: false,
     term_search_fuel: 400,
+    term_search_borrowck: true,
 };
 
 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs
index f138938b02..c37900478e 100644
--- a/crates/ide-completion/src/tests/item_list.rs
+++ b/crates/ide-completion/src/tests/item_list.rs
@@ -123,7 +123,6 @@ fn after_unsafe_token() {
     check(
         r#"unsafe $0"#,
         expect![[r#"
-            kw async
             kw fn
             kw impl
             kw trait
@@ -131,17 +130,6 @@ fn after_unsafe_token() {
     );
 }
 
-#[test]
-fn after_async_token() {
-    check(
-        r#"async $0"#,
-        expect![[r#"
-            kw fn
-            kw unsafe
-        "#]],
-    );
-}
-
 #[test]
 fn after_visibility() {
     check(
@@ -169,7 +157,6 @@ fn after_visibility_unsafe() {
     check(
         r#"pub unsafe $0"#,
         expect![[r#"
-            kw async
             kw fn
             kw trait
         "#]],
@@ -183,7 +170,6 @@ fn in_impl_assoc_item_list() {
         expect![[r#"
             ma makro!(…)  macro_rules! makro
             md module
-            kw async
             kw const
             kw crate::
             kw fn
@@ -203,7 +189,6 @@ fn in_impl_assoc_item_list_after_attr() {
         expect![[r#"
             ma makro!(…)  macro_rules! makro
             md module
-            kw async
             kw const
             kw crate::
             kw fn
@@ -223,7 +208,6 @@ fn in_trait_assoc_item_list() {
         expect![[r#"
             ma makro!(…) macro_rules! makro
             md module
-            kw async
             kw const
             kw crate::
             kw fn
@@ -241,7 +225,6 @@ fn in_trait_assoc_fn_missing_body() {
         expect![[r#"
             ma makro!(…) macro_rules! makro
             md module
-            kw async
             kw const
             kw crate::
             kw fn
@@ -259,7 +242,6 @@ fn in_trait_assoc_const_missing_body() {
         expect![[r#"
             ma makro!(…) macro_rules! makro
             md module
-            kw async
             kw const
             kw crate::
             kw fn
@@ -277,7 +259,6 @@ fn in_trait_assoc_type_aliases_missing_ty() {
         expect![[r#"
             ma makro!(…) macro_rules! makro
             md module
-            kw async
             kw const
             kw crate::
             kw fn