Repository: rust-analyzer/rust-analyzer
Pull request #11686 (2022-03-12 03:07:36)

feat: Enum variant field completion, enum variant / struct consistency
This addresses several related inconsistencies:
 - tuple structs use tab stops instead of placeholders
 - tuple structs display in the completion menu as `Struct {…}` instead of `Struct(…)`
 - enum variants don't receive field completions at all
 - enum variants display differently from structs in the completion menu

Also, structs now display their type in the completion detail rather than the raw snippet text to be inserted.

As far as what's user-visible, that looks like this:

| | Menu | Completion | Detail |
|-|-|-|-|
| Record struct (old) | `Struct {…}` | `Struct { x: ${1:()}, y: ${2:()} }$0` | `Struct { x: ${1:()}, y: ${2:()} }$0` |
| Record struct (new) | `Struct {…}` | `Struct { x: ${1:()}, y: ${2:()} }$0` | `Struct { x: i32, y: i32 }` |
| Tuple struct (old) | `Struct {…}`  | `Struct($1, $2)$0` | `Struct($1, $2)` |
| Tuple struct (new) | `Struct(…)` | `Struct(${1:()}, ${2:()})$0` | `Struct(i32, i32)` |
| Unit variant (old) | `Variant` | `Variant` | `()` |
| Unit variant (new) | `Variant` | `Variant$0` | `Variant` |
| Record variant (old) | `Variant` | `Variant` | `{x: i32, y: i32}` |
| Record variant (new) | `Variant {…}` | `Variant { x: ${1:()}, y: ${2:()} }$0` | `Variant { x: i32, y: i32 }` |
| Tuple variant (old) | `Variant(…)` | `Variant($0)` | `(i32, i32)` |
| Tuple variant (new) | `Variant(…)` | `Variant(${1:()}, ${2:()})$0` | `Variant(i32, i32)` |

Additionally, tuple variants no longer set `triggers_call_info` because it conflicts with placeholder generation, and tuple variants that require a qualified path should now use the qualified path.

Internally, this also lets us break the general "format an item with fields on it" code out into a shared module, so that means it'll be a lot easier to implement features like #11568.



Patch:
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 322a9b820a..8ecc5567b4 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -12,7 +12,6 @@ use crate::FileId;
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct InlayHintsConfig {
-    pub render_colons: bool,
     pub type_hints: bool,
     pub parameter_hints: bool,
     pub chaining_hints: bool,
@@ -575,7 +574,6 @@ mod tests {
     use crate::{fixture, inlay_hints::InlayHintsConfig};
 
     const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
-        render_colons: true,
         type_hints: true,
         parameter_hints: true,
         chaining_hints: true,
@@ -592,7 +590,6 @@ mod tests {
     fn check_params(ra_fixture: &str) {
         check_with_config(
             InlayHintsConfig {
-                render_colons: true,
                 parameter_hints: true,
                 type_hints: false,
                 chaining_hints: false,
@@ -607,7 +604,6 @@ mod tests {
     fn check_types(ra_fixture: &str) {
         check_with_config(
             InlayHintsConfig {
-                render_colons: true,
                 parameter_hints: false,
                 type_hints: true,
                 chaining_hints: false,
@@ -622,7 +618,6 @@ mod tests {
     fn check_chains(ra_fixture: &str) {
         check_with_config(
             InlayHintsConfig {
-                render_colons: true,
                 parameter_hints: false,
                 type_hints: false,
                 chaining_hints: true,
@@ -654,7 +649,6 @@ mod tests {
     fn hints_disabled() {
         check_with_config(
             InlayHintsConfig {
-                render_colons: true,
                 type_hints: false,
                 parameter_hints: false,
                 chaining_hints: false,
@@ -1110,7 +1104,6 @@ fn main() {
         let inlay_hints = analysis
             .inlay_hints(
                 &InlayHintsConfig {
-                    render_colons: true,
                     parameter_hints: false,
                     type_hints: true,
                     chaining_hints: false,
@@ -1420,7 +1413,6 @@ fn main() {
     fn skip_constructor_and_enum_type_hints() {
         check_with_config(
             InlayHintsConfig {
-                render_colons: true,
                 type_hints: true,
                 parameter_hints: true,
                 chaining_hints: true,
@@ -1598,7 +1590,6 @@ fn main() {
     fn chaining_hints_ignore_comments() {
         check_expect(
             InlayHintsConfig {
-                render_colons: true,
                 parameter_hints: false,
                 type_hints: false,
                 chaining_hints: true,
@@ -1656,7 +1647,6 @@ fn main() {
     fn struct_access_chaining_hints() {
         check_expect(
             InlayHintsConfig {
-                render_colons: true,
                 parameter_hints: false,
                 type_hints: false,
                 chaining_hints: true,
@@ -1702,7 +1692,6 @@ fn main() {
     fn generic_chaining_hints() {
         check_expect(
             InlayHintsConfig {
-                render_colons: true,
                 parameter_hints: false,
                 type_hints: false,
                 chaining_hints: true,
@@ -1749,7 +1738,6 @@ fn main() {
     fn shorten_iterator_chaining_hints() {
         check_expect(
             InlayHintsConfig {
-                render_colons: true,
                 parameter_hints: false,
                 type_hints: false,
                 chaining_hints: true,
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index ef3487545c..31d85c60e7 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -105,7 +105,6 @@ impl StaticIndex<'_> {
             .analysis
             .inlay_hints(
                 &InlayHintsConfig {
-                    render_colons: true,
                     type_hints: true,
                     parameter_hints: true,
                     chaining_hints: true,
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs
index ac782352fc..4704e842e6 100644
--- a/crates/ide_completion/src/completions/keyword.rs
+++ b/crates/ide_completion/src/completions/keyword.rs
@@ -163,7 +163,6 @@ fn add_keyword(acc: &mut Completions, ctx: &CompletionContext, kw: &str, snippet
     match ctx.config.snippet_cap {
         Some(cap) => {
             if snippet.ends_with('}') && ctx.incomplete_let {
-                // complete block expression snippets with a trailing semicolon, if inside an incomplete let
                 cov_mark::hit!(let_semi);
                 item.insert_snippet(cap, format!("{};", snippet));
             } else {
diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs
index b8a50442bc..878d72ea0f 100644
--- a/crates/ide_completion/src/completions/lifetime.rs
+++ b/crates/ide_completion/src/completions/lifetime.rs
@@ -8,7 +8,7 @@
 //! show up for normal completions, or they won't show completions other than lifetimes depending
 //! on the fixture input.
 use hir::{known, ScopeDef};
-use syntax::{ast, TokenText};
+use syntax::ast;
 
 use crate::{
     completions::Completions,
@@ -19,24 +19,24 @@ use crate::{
 pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) {
     let lp = match &ctx.lifetime_ctx {
         Some(LifetimeContext::Lifetime) => None,
-        Some(LifetimeContext::LifetimeParam { is_decl: false, param }) => Some(param),
+        Some(LifetimeContext::LifetimeParam(param)) => param.as_ref(),
         _ => return,
     };
+    let lp_string;
     let param_lifetime = match (&ctx.name_syntax, lp.and_then(|lp| lp.lifetime())) {
         (Some(ast::NameLike::Lifetime(lt)), Some(lp)) if lp == lt.clone() => return,
-        (Some(_), Some(lp)) => Some(lp),
+        (Some(_), Some(lp)) => {
+            lp_string = lp.to_string();
+            Some(&*lp_string)
+        }
         _ => None,
     };
-    let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text);
-    let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str);
 
     ctx.scope.process_all_names(&mut |name, res| {
-        if matches!(
-            res,
-            ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))
-                 if param_lifetime != Some(&*name.to_smol_str())
-        ) {
-            acc.add_lifetime(ctx, name);
+        if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res {
+            if param_lifetime != Some(&*name.to_smol_str()) {
+                acc.add_lifetime(ctx, name);
+            }
         }
     });
     if param_lifetime.is_none() {
@@ -195,12 +195,6 @@ fn foo2<'lifetime, T>() where T: Trait<Item = 'a$0> {}
     fn complete_lifetime_in_param_list() {
         check(
             r#"
-fn foo<'$0>() {}
-"#,
-            expect![[r#""#]],
-        );
-        check(
-            r#"
 fn foo<'a$0>() {}
 "#,
             expect![[r#""#]],
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs
index acd02616b1..86b1f534b2 100644
--- a/crates/ide_completion/src/completions/qualified_path.rs
+++ b/crates/ide_completion/src/completions/qualified_path.rs
@@ -580,8 +580,8 @@ impl Foo {
 }
 "#,
             expect![[r#"
-                ev Bar    ()
-                ev Baz    ()
+                ev Bar    Bar
+                ev Baz    Baz
                 me foo(…) fn(self)
             "#]],
         );
@@ -626,7 +626,7 @@ fn main() {
 }
 "#,
             expect![[r#"
-                ev Bar ()
+                ev Bar Bar
             "#]],
         );
     }
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index bf841ee2b7..51bbd66ff3 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -24,8 +24,8 @@ use text_edit::Indel;
 
 use crate::{
     patterns::{
-        determine_location, determine_prev_sibling, for_is_prev2, is_in_loop_body, previous_token,
-        ImmediateLocation, ImmediatePrevSibling,
+        determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block,
+        is_in_loop_body, previous_token, ImmediateLocation, ImmediatePrevSibling,
     },
     CompletionConfig,
 };
@@ -94,7 +94,7 @@ pub(super) struct PatternContext {
 
 #[derive(Debug)]
 pub(super) enum LifetimeContext {
-    LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
+    LifetimeParam(Option<ast::LifetimeParam>),
     Lifetime,
     LabelRef,
     LabelDef,
@@ -115,7 +115,6 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) db: &'a RootDatabase,
     pub(super) config: &'a CompletionConfig,
     pub(super) position: FilePosition,
-
     /// The token before the cursor, in the original file.
     pub(super) original_token: SyntaxToken,
     /// The token before the cursor, in the macro-expanded file.
@@ -147,22 +146,32 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) existing_derives: FxHashSet<hir::Macro>,
 
     pub(super) locals: Vec<(Name, Local)>,
+
+    no_completion_required: bool,
 }
 
 impl<'a> CompletionContext<'a> {
+    /// Checks whether completions in that particular case don't make much sense.
+    /// Examples:
+    /// - `fn $0` -- we expect function name, it's unlikely that "hint" will be helpful.
+    ///   Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names.
+    /// - `for _ i$0` -- obviously, it'll be "in" keyword.
+    pub(crate) fn no_completion_required(&self) -> bool {
+        self.no_completion_required
+    }
+
     /// The range of the identifier that is being completed.
     pub(crate) fn source_range(&self) -> TextRange {
         // check kind of macro-expanded token, but use range of original token
         let kind = self.token.kind();
-        match kind {
-            CHAR => {
-                // assume we are completing a lifetime but the user has only typed the '
-                cov_mark::hit!(completes_if_lifetime_without_idents);
-                TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
-            }
-            IDENT | LIFETIME_IDENT | UNDERSCORE => self.original_token.text_range(),
-            _ if kind.is_keyword() => self.original_token.text_range(),
-            _ => TextRange::empty(self.position.offset),
+        if kind == IDENT || kind == LIFETIME_IDENT || kind == UNDERSCORE || kind.is_keyword() {
+            self.original_token.text_range()
+        } else if kind == CHAR {
+            // assume we are completing a lifetime but the user has only typed the '
+            cov_mark::hit!(completes_if_lifetime_without_idents);
+            TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
+        } else {
+            TextRange::empty(self.position.offset)
         }
     }
 
@@ -444,6 +453,7 @@ impl<'a> CompletionContext<'a> {
             path_context: None,
             locals,
             incomplete_let: false,
+            no_completion_required: false,
             existing_derives: Default::default(),
         };
         ctx.expand_and_fill(
@@ -730,16 +740,14 @@ impl<'a> CompletionContext<'a> {
     ) {
         let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
         let syntax_element = NodeOrToken::Token(fake_ident_token);
-        if for_is_prev2(syntax_element.clone()) {
-            // for pat $0
-            // there is nothing to complete here except `in` keyword
-            // don't bother populating the context
-            // FIXME: the completion calculations should end up good enough
-            // such that this special case becomes unnecessary
-            return;
-        }
-
         self.previous_token = previous_token(syntax_element.clone());
+        self.no_completion_required = {
+            let inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone());
+            let fn_is_prev = self.previous_token_is(T![fn]);
+            let for_is_prev2 = for_is_prev2(syntax_element.clone());
+            (fn_is_prev && !inside_impl_trait_block) || for_is_prev2
+        };
+
         self.fake_attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast);
 
         self.incomplete_let =
@@ -747,7 +755,9 @@ impl<'a> CompletionContext<'a> {
                 it.syntax().text_range().end() == syntax_element.text_range().end()
             });
 
-        (self.expected_type, self.expected_name) = self.expected_type_and_name();
+        let (expected_type, expected_name) = self.expected_type_and_name();
+        self.expected_type = expected_type;
+        self.expected_name = expected_name;
 
         // Overwrite the path kind for derives
         if let Some((original_file, file_with_fake_ident, offset)) = derive_ctx {
@@ -798,7 +808,8 @@ impl<'a> CompletionContext<'a> {
 
         match name_like {
             ast::NameLike::Lifetime(lifetime) => {
-                self.lifetime_ctx = Self::classify_lifetime(&self.sema, original_file, lifetime);
+                self.lifetime_ctx =
+                    Self::classify_lifetime(&self.sema, original_file, lifetime, offset);
             }
             ast::NameLike::NameRef(name_ref) => {
                 if let Some((path_ctx, pat_ctx)) =
@@ -815,9 +826,10 @@ impl<'a> CompletionContext<'a> {
     }
 
     fn classify_lifetime(
-        _sema: &Semantics<RootDatabase>,
-        _original_file: &SyntaxNode,
+        sema: &Semantics<RootDatabase>,
+        original_file: &SyntaxNode,
         lifetime: ast::Lifetime,
+        offset: TextSize,
     ) -> Option<LifetimeContext> {
         let parent = lifetime.syntax().parent()?;
         if parent.kind() == ERROR {
@@ -826,10 +838,7 @@ impl<'a> CompletionContext<'a> {
 
         Some(match_ast! {
             match parent {
-                ast::LifetimeParam(param) => LifetimeContext::LifetimeParam {
-                    is_decl: param.lifetime().as_ref() == Some(&lifetime),
-                    param
-                },
+                ast::LifetimeParam(_) => LifetimeContext::LifetimeParam(sema.find_node_at_offset_with_macros(original_file, offset)),
                 ast::BreakExpr(_) => LifetimeContext::LabelRef,
                 ast::ContinueExpr(_) => LifetimeContext::LabelRef,
                 ast::Label(_) => LifetimeContext::LabelDef,
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index 422bda6460..86a6947b1d 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -144,6 +144,12 @@ pub fn completions(
 ) -> Option<Completions> {
     let ctx = CompletionContext::new(db, position, config)?;
 
+    if ctx.no_completion_required() {
+        cov_mark::hit!(no_completion_required);
+        // No work required here.
+        return None;
+    }
+
     let mut acc = Completions::default();
     completions::attribute::complete_attribute(&mut acc, &ctx);
     completions::attribute::complete_derive(&mut acc, &ctx);
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index 8a53d6e4d4..41e2423f46 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -15,7 +15,7 @@ use syntax::{
 };
 
 #[cfg(test)]
-use crate::tests::check_pattern_is_applicable;
+use crate::tests::{check_pattern_is_applicable, check_pattern_is_not_applicable};
 
 /// Immediate previous node to what we are completing.
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -291,6 +291,24 @@ fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Op
     syntax.covering_element(range).ancestors().find_map(N::cast)
 }
 
+pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
+    // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
+    // where we only check the first parent with different text range.
+    element
+        .ancestors()
+        .find(|it| it.kind() == IMPL)
+        .map(|it| ast::Impl::cast(it).unwrap())
+        .map(|it| it.trait_().is_some())
+        .unwrap_or(false)
+}
+#[test]
+fn test_inside_impl_trait_block() {
+    check_pattern_is_applicable(r"impl Foo for Bar { f$0 }", inside_impl_trait_block);
+    check_pattern_is_applicable(r"impl Foo for Bar { fn f$0 }", inside_impl_trait_block);
+    check_pattern_is_not_applicable(r"impl A { f$0 }", inside_impl_trait_block);
+    check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block);
+}
+
 pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
     element.into_token().and_then(previous_non_trivia_token)
 }
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs
index e7a5426a26..e8ebb3e337 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -8,6 +8,7 @@ pub(crate) mod const_;
 pub(crate) mod pattern;
 pub(crate) mod type_alias;
 pub(crate) mod struct_literal;
+pub(crate) mod compound;
 
 mod builder_ext;
 
@@ -428,14 +429,14 @@ fn main() { Foo::Fo$0 }
             expect![[r#"
                 [
                     CompletionItem {
-                        label: "Foo",
+                        label: "Foo {…}",
                         source_range: 54..56,
                         delete: 54..56,
-                        insert: "Foo",
+                        insert: "Foo { x: ${1:()}, y: ${2:()} }$0",
                         kind: SymbolKind(
                             Variant,
                         ),
-                        detail: "{x: i32, y: i32}",
+                        detail: "Foo { x: i32, y: i32 }",
                     },
                 ]
             "#]],
@@ -443,7 +444,7 @@ fn main() { Foo::Fo$0 }
     }
 
     #[test]
-    fn enum_detail_doesnt_include_tuple_fields() {
+    fn enum_detail_includes_tuple_fields() {
         check(
             r#"
 enum Foo { Foo (i32, i32) }
@@ -457,13 +458,11 @@ fn main() { Foo::Fo$0 }
                         label: "Foo(…)",
                         source_range: 46..48,
                         delete: 46..48,
-                        insert: "Foo($0)",
+                        insert: "Foo(${1:()}, ${2:()})$0",
                         kind: SymbolKind(
                             Variant,
                         ),
-                        lookup: "Foo",
-                        detail: "(i32, i32)",
-                        trigger_call_info: true,
+                        detail: "Foo(i32, i32)",
                     },
                 ]
             "#]],
@@ -510,7 +509,7 @@ fn main() { fo$0 }
     }
 
     #[test]
-    fn enum_detail_just_parentheses_for_unit() {
+    fn enum_detail_just_name_for_unit() {
         check(
             r#"
 enum Foo { Foo }
@@ -524,11 +523,11 @@ fn main() { Foo::Fo$0 }
                         label: "Foo",
                         source_range: 35..37,
                         delete: 35..37,
-                        insert: "Foo",
+                        insert: "Foo$0",
                         kind: SymbolKind(
                             Variant,
                         ),
-                        detail: "()",
+                        detail: "Foo",
                     },
                 ]
             "#]],
@@ -572,15 +571,15 @@ fn main() { let _: m::Spam = S$0 }
                         ),
                     },
                     CompletionItem {
-                        label: "Spam::Bar(…)",
+                        label: "m::Spam::Bar(…)",
                         source_range: 75..76,
                         delete: 75..76,
-                        insert: "Spam::Bar($0)",
+                        insert: "m::Spam::Bar(${1:()})$0",
                         kind: SymbolKind(
                             Variant,
                         ),
                         lookup: "Spam::Bar",
-                        detail: "(i32)",
+                        detail: "m::Spam::Bar(i32)",
                         relevance: CompletionRelevance {
                             exact_name_match: false,
                             type_match: Some(
@@ -591,18 +590,17 @@ fn main() { let _: m::Spam = S$0 }
                             is_private_editable: false,
                             exact_postfix_snippet_match: false,
                         },
-                        trigger_call_info: true,
                     },
                     CompletionItem {
                         label: "m::Spam::Foo",
                         source_range: 75..76,
                         delete: 75..76,
-                        insert: "m::Spam::Foo",
+                        insert: "m::Spam::Foo$0",
                         kind: SymbolKind(
                             Variant,
                         ),
                         lookup: "Spam::Foo",
-                        detail: "()",
+                        detail: "m::Spam::Foo",
                         relevance: CompletionRelevance {
                             exact_name_match: false,
                             type_match: Some(
@@ -787,11 +785,11 @@ use self::E::*;
                         label: "V",
                         source_range: 10..12,
                         delete: 10..12,
-                        insert: "V",
+                        insert: "V$0",
                         kind: SymbolKind(
                             Variant,
                         ),
-                        detail: "()",
+                        detail: "V",
                         documentation: Documentation(
                             "variant docs",
                         ),
diff --git a/crates/ide_completion/src/render/builder_ext.rs b/crates/ide_completion/src/render/builder_ext.rs
index 653515fec2..70767a2a9c 100644
--- a/crates/ide_completion/src/render/builder_ext.rs
+++ b/crates/ide_completion/src/render/builder_ext.rs
@@ -8,6 +8,7 @@ use crate::{context::PathKind, item::Builder, patterns::ImmediateLocation, Compl
 #[derive(Debug)]
 pub(super) enum Params {
     Named(Option<hir::SelfParam>, Vec<hir::Param>),
+    #[allow(dead_code)]
     Anonymous(usize),
 }
 
diff --git a/crates/ide_completion/src/render/compound.rs b/crates/ide_completion/src/render/compound.rs
new file mode 100644
index 0000000000..c7f3bd1f79
--- /dev/null
+++ b/crates/ide_completion/src/render/compound.rs
@@ -0,0 +1,95 @@
+//! Code common to structs, unions, and enum variants.
+
+use crate::render::RenderContext;
+use hir::{db::HirDatabase, HasAttrs, HasVisibility, HirDisplay, StructKind};
+use ide_db::SnippetCap;
+use itertools::Itertools;
+use syntax::SmolStr;
+
+/// A rendered struct, union, or enum variant, split into fields for actual
+/// auto-completion (`literal`, using `field: ()`) and display in the
+/// completions menu (`detail`, using `field: type`).
+pub(crate) struct RenderedCompound {
+    pub(crate) literal: String,
+    pub(crate) detail: String,
+}
+
+/// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for
+/// the `name` argument for an anonymous type.
+pub(crate) fn render_record(
+    db: &dyn HirDatabase,
+    snippet_cap: Option<SnippetCap>,
+    fields: &[hir::Field],
+    name: Option<&str>,
+) -> RenderedCompound {
+    let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
+        if snippet_cap.is_some() {
+            f(&format_args!("{}: ${{{}:()}}", field.name(db), idx + 1))
+        } else {
+            f(&format_args!("{}: ()", field.name(db)))
+        }
+    });
+
+    let types = fields.iter().format_with(", ", |field, f| {
+        f(&format_args!("{}: {}", field.name(db), field.ty(db).display(db)))
+    });
+
+    RenderedCompound {
+        literal: format!("{} {{ {} }}", name.unwrap_or(""), completions),
+        detail: format!("{} {{ {} }}", name.unwrap_or(""), types),
+    }
+}
+
+/// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for
+/// the `name` argument for an anonymous type.
+pub(crate) fn render_tuple(
+    db: &dyn HirDatabase,
+    snippet_cap: Option<SnippetCap>,
+    fields: &[hir::Field],
+    name: Option<&str>,
+) -> RenderedCompound {
+    let completions = fields.iter().enumerate().format_with(", ", |(idx, _), f| {
+        if snippet_cap.is_some() {
+            f(&format_args!("${{{}:()}}", idx + 1))
+        } else {
+            f(&format_args!("()"))
+        }
+    });
+
+    let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db)));
+
+    RenderedCompound {
+        literal: format!("{}({})", name.unwrap_or(""), completions),
+        detail: format!("{}({})", name.unwrap_or(""), types),
+    }
+}
+
+/// Find all the visible fields in a given list. Returns the list of visible
+/// fields, plus a boolean for whether the list is comprehensive (contains no
+/// private fields and its item is not marked `#[non_exhaustive]`).
+pub(crate) fn visible_fields(
+    ctx: &RenderContext<'_>,
+    fields: &[hir::Field],
+    item: impl HasAttrs,
+) -> Option<(Vec<hir::Field>, bool)> {
+    let module = ctx.completion.module?;
+    let n_fields = fields.len();
+    let fields = fields
+        .iter()
+        .filter(|field| field.is_visible_from(ctx.db(), module))
+        .copied()
+        .collect::<Vec<_>>();
+
+    let fields_omitted =
+        n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
+    Some((fields, fields_omitted))
+}
+
+/// Format a struct, etc. literal option for display in the completions menu.
+pub(crate) fn format_literal_label(name: &str, kind: StructKind) -> SmolStr {
+    match kind {
+        StructKind::Tuple => SmolStr::from_iter([name, "(…)"]),
+        StructKind::Record => SmolStr::from_iter([name, " {…}"]),
+        StructKind::Unit => name.into(),
+    }
+}
diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs
index 914ace910d..5b485005d3 100644
--- a/crates/ide_completion/src/render/enum_variant.rs
+++ b/crates/ide_completion/src/render/enum_variant.rs
@@ -1,13 +1,15 @@
 //! Renderer for `enum` variants.
 
-use hir::{db::HirDatabase, HasAttrs, HirDisplay, StructKind};
+use hir::{HasAttrs, StructKind};
 use ide_db::SymbolKind;
-use itertools::Itertools;
 use syntax::SmolStr;
 
 use crate::{
     item::{CompletionItem, ImportEdit},
-    render::{builder_ext::Params, compute_ref_match, compute_type_match, RenderContext},
+    render::{
+        compound::{format_literal_label, render_record, render_tuple, RenderedCompound},
+        compute_ref_match, compute_type_match, RenderContext,
+    },
     CompletionRelevance,
 };
 
@@ -46,20 +48,42 @@ fn render(
     let qualified_name = qualified_name.to_string();
     let short_qualified_name: SmolStr = short_qualified_name.to_string().into();
 
-    let mut item = CompletionItem::new(SymbolKind::Variant, ctx.source_range(), qualified_name);
+    let mut rendered = match variant_kind {
+        StructKind::Tuple => {
+            render_tuple(db, ctx.snippet_cap(), &variant.fields(db), Some(&qualified_name))
+        }
+        StructKind::Record => {
+            render_record(db, ctx.snippet_cap(), &variant.fields(db), Some(&qualified_name))
+        }
+        StructKind::Unit => {
+            RenderedCompound { literal: qualified_name.clone(), detail: qualified_name.clone() }
+        }
+    };
+
+    if ctx.snippet_cap().is_some() {
+        rendered.literal.push_str("$0");
+    }
+
+    let mut item = CompletionItem::new(
+        SymbolKind::Variant,
+        ctx.source_range(),
+        format_literal_label(&qualified_name, variant_kind),
+    );
+
     item.set_documentation(variant.docs(db))
         .set_deprecated(ctx.is_deprecated(variant))
-        .detail(detail(db, variant, variant_kind));
+        .detail(rendered.detail);
+
+    match ctx.snippet_cap() {
+        Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal),
+        None => item.insert_text(rendered.literal),
+    };
 
     if let Some(import_to_add) = import_to_add {
         item.add_import(import_to_add);
     }
 
-    if variant_kind == hir::StructKind::Tuple {
-        cov_mark::hit!(inserts_parens_for_tuple_enums);
-        let params = Params::Anonymous(variant.fields(db).len());
-        item.add_call_parens(completion, short_qualified_name, params);
-    } else if qualified {
+    if qualified {
         item.lookup_by(short_qualified_name);
     }
 
@@ -75,50 +99,3 @@ fn render(
 
     item.build()
 }
-
-fn detail(db: &dyn HirDatabase, variant: hir::Variant, variant_kind: StructKind) -> String {
-    let detail_types = variant.fields(db).into_iter().map(|field| (field.name(db), field.ty(db)));
-
-    match variant_kind {
-        hir::StructKind::Tuple | hir::StructKind::Unit => {
-            format!("({})", detail_types.format_with(", ", |(_, t), f| f(&t.display(db))))
-        }
-        hir::StructKind::Record => {
-            format!(
-                "{{{}}}",
-                detail_types.format_with(", ", |(n, t), f| {
-                    f(&n)?;
-                    f(&": ")?;
-                    f(&t.display(db))
-                }),
-            )
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::tests::check_edit;
-
-    #[test]
-    fn inserts_parens_for_tuple_enums() {
-        cov_mark::check!(inserts_parens_for_tuple_enums);
-        check_edit(
-            "Some",
-            r#"
-enum Option<T> { Some(T), None }
-use Option::*;
-fn main() -> Option<i32> {
-    Som$0
-}
-"#,
-            r#"
-enum Option<T> { Some(T), None }
-use Option::*;
-fn main() -> Option<i32> {
-    Some($0)
-}
-"#,
-        );
-    }
-}
diff --git a/crates/ide_completion/src/render/struct_literal.rs b/crates/ide_completion/src/render/struct_literal.rs
index 3bc94fa782..a686be6691 100644
--- a/crates/ide_completion/src/render/struct_literal.rs
+++ b/crates/ide_completion/src/render/struct_literal.rs
@@ -1,11 +1,15 @@
 //! Renderer for `struct` literal.
 
-use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
-use ide_db::SnippetCap;
-use itertools::Itertools;
+use hir::{HasAttrs, Name, StructKind};
 use syntax::SmolStr;
 
-use crate::{render::RenderContext, CompletionItem, CompletionItemKind};
+use crate::{
+    render::compound::{
+        format_literal_label, render_record, render_tuple, visible_fields, RenderedCompound,
+    },
+    render::RenderContext,
+    CompletionItem, CompletionItemKind,
+};
 
 pub(crate) fn render_struct_literal(
     ctx: RenderContext<'_>,
@@ -25,29 +29,31 @@ pub(crate) fn render_struct_literal(
 
     let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_smol_str();
 
-    let literal = render_literal(&ctx, path, &name, strukt.kind(ctx.db()), &visible_fields)?;
+    let rendered = render_literal(&ctx, path, &name, strukt.kind(ctx.db()), &visible_fields)?;
 
-    Some(build_completion(ctx, name, literal, strukt))
+    Some(build_completion(&ctx, name, rendered, strukt.kind(ctx.db()), strukt))
 }
 
 fn build_completion(
-    ctx: RenderContext<'_>,
+    ctx: &RenderContext<'_>,
     name: SmolStr,
-    literal: String,
+    rendered: RenderedCompound,
+    kind: StructKind,
     def: impl HasAttrs + Copy,
 ) -> CompletionItem {
     let mut item = CompletionItem::new(
         CompletionItemKind::Snippet,
         ctx.source_range(),
-        SmolStr::from_iter([&name, " {…}"]),
+        format_literal_label(&name, kind),
     );
+
     item.set_documentation(ctx.docs(def))
         .set_deprecated(ctx.is_deprecated(def))
-        .detail(&literal)
+        .detail(&rendered.detail)
         .set_relevance(ctx.completion_relevance());
     match ctx.snippet_cap() {
-        Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
-        None => item.insert_text(literal),
+        Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal),
+        None => item.insert_text(rendered.literal),
     };
     item.build()
 }
@@ -58,7 +64,7 @@ fn render_literal(
     name: &str,
     kind: StructKind,
     fields: &[hir::Field],
-) -> Option<String> {
+) -> Option<RenderedCompound> {
     let path_string;
 
     let qualified_name = if let Some(path) = path {
@@ -68,69 +74,18 @@ fn render_literal(
         name
     };
 
-    let mut literal = match kind {
+    let mut rendered = match kind {
         StructKind::Tuple if ctx.snippet_cap().is_some() => {
-            render_tuple_as_literal(fields, qualified_name)
+            render_tuple(ctx.db(), ctx.snippet_cap(), fields, Some(qualified_name))
         }
         StructKind::Record => {
-            render_record_as_literal(ctx.db(), ctx.snippet_cap(), fields, qualified_name)
+            render_record(ctx.db(), ctx.snippet_cap(), fields, Some(qualified_name))
         }
         _ => return None,
     };
 
     if ctx.snippet_cap().is_some() {
-        literal.push_str("$0");
+        rendered.literal.push_str("$0");
     }
-    Some(literal)
-}
-
-fn render_record_as_literal(
-    db: &dyn HirDatabase,
-    snippet_cap: Option<SnippetCap>,
-    fields: &[hir::Field],
-    name: &str,
-) -> String {
-    let fields = fields.iter();
-    if snippet_cap.is_some() {
-        format!(
-            "{name} {{ {} }}",
-            fields
-                .enumerate()
-                .map(|(idx, field)| format!("{}: ${{{}:()}}", field.name(db), idx + 1))
-                .format(", "),
-            name = name
-        )
-    } else {
-        format!(
-            "{name} {{ {} }}",
-            fields.map(|field| format!("{}: ()", field.name(db))).format(", "),
-            name = name
-        )
-    }
-}
-
-fn render_tuple_as_literal(fields: &[hir::Field], name: &str) -> String {
-    format!(
-        "{name}({})",
-        fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
-        name = name
-    )
-}
-
-fn visible_fields(
-    ctx: &RenderContext<'_>,
-    fields: &[hir::Field],
-    item: impl HasAttrs,
-) -> Option<(Vec<hir::Field>, bool)> {
-    let module = ctx.completion.module?;
-    let n_fields = fields.len();
-    let fields = fields
-        .iter()
-        .filter(|field| field.is_visible_from(ctx.db(), module))
-        .copied()
-        .collect::<Vec<_>>();
-
-    let fields_omitted =
-        n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
-    Some((fields, fields_omitted))
+    Some(rendered)
 }
diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs
index 948873ae2d..1148c6c35c 100644
--- a/crates/parser/src/grammar/generic_args.rs
+++ b/crates/parser/src/grammar/generic_args.rs
@@ -72,24 +72,28 @@ fn lifetime_arg(p: &mut Parser) {
     m.complete(p, LIFETIME_ARG);
 }
 
-pub(super) fn const_arg_expr(p: &mut Parser) {
-    // The tests in here are really for `const_arg`, which wraps the content
-    // CONST_ARG.
+// test const_arg
+// type T = S<92>;
+pub(super) fn const_arg(p: &mut Parser) {
+    let m = p.start();
     match p.current() {
         // test const_arg_block
         // type T = S<{90 + 2}>;
         T!['{'] => {
             expressions::block_expr(p);
+            m.complete(p, CONST_ARG);
         }
         // test const_arg_literal
         // type T = S<"hello", 0xdeadbeef>;
         k if k.is_literal() => {
             expressions::literal(p);
+            m.complete(p, CONST_ARG);
         }
         // test const_arg_bool_literal
         // type T = S<true>;
         T![true] | T![false] => {
             expressions::literal(p);
+            m.complete(p, CONST_ARG);
         }
         // test const_arg_negative_number
         // type T = S<-92>;
@@ -98,24 +102,19 @@ pub(super) fn const_arg_expr(p: &mut Parser) {
             p.bump(T![-]);
             expressions::literal(p);
             lm.complete(p, PREFIX_EXPR);
+            m.complete(p, CONST_ARG);
         }
+        // test const_arg_path
+        // struct S<const N: u32 = u32::MAX>;
         _ => {
-            // This shouldn't be hit by `const_arg`
             let lm = p.start();
             paths::use_path(p);
             lm.complete(p, PATH_EXPR);
+            m.complete(p, CONST_ARG);
         }
     }
 }
 
-// test const_arg
-// type T = S<92>;
-pub(super) fn const_arg(p: &mut Parser) {
-    let m = p.start();
-    const_arg_expr(p);
-    m.complete(p, CONST_ARG);
-}
-
 fn type_arg(p: &mut Parser) {
     let m = p.start();
     types::type_(p);
diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs
index fbd514078d..1009c67ffa 100644
--- a/crates/parser/src/grammar/generic_params.rs
+++ b/crates/parser/src/grammar/generic_params.rs
@@ -79,16 +79,10 @@ fn const_param(p: &mut Parser, m: Marker) {
     }
 
     if p.at(T![=]) {
-        // test const_param_default_literal
+        // test const_param_defaults
         // struct A<const N: i32 = -1>;
         p.bump(T![=]);
-
-        // test const_param_default_expression
-        // struct A<const N: i32 = { 1 }>;
-
-        // test const_param_default_path
-        // struct A<const N: i32 = i32::MAX>;
-        generic_args::const_arg_expr(p);
+        generic_args::const_arg(p);
     }
 
     m.complete(p, CONST_PARAM);
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index e50bcbb690..1194fd9faf 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -242,8 +242,6 @@ config_data! {
         /// `#rust-analyzer.hoverActions.enable#` is set.
         hoverActions_run: bool             = "true",
 
-        /// Whether to render trailing colons for parameter hints, and trailing colons for parameter hints.
-        inlayHints_renderColons: bool               = "true",
         /// Whether to show inlay type hints for method chains.
         inlayHints_chainingHints: bool              = "true",
         /// Maximum length for inlay hints. Set to null to have an unlimited length.
@@ -848,7 +846,6 @@ impl Config {
     }
     pub fn inlay_hints(&self) -> InlayHintsConfig {
         InlayHintsConfig {
-            render_colons: self.data.inlayHints_renderColons,
             type_hints: self.data.inlayHints_typeHints,
             parameter_hints: self.data.inlayHints_parameterHints,
             chaining_hints: self.data.inlayHints_chainingHints,
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index b5e9776000..249e861f9b 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -1331,12 +1331,11 @@ pub(crate) fn handle_inlay_hints(
             )
         })
         .transpose()?;
-    let inlay_hints_config = snap.config.inlay_hints();
     Ok(snap
         .analysis
-        .inlay_hints(&inlay_hints_config, file_id, range)?
+        .inlay_hints(&snap.config.inlay_hints(), file_id, range)?
         .into_iter()
-        .map(|it| to_proto::inlay_hint(inlay_hints_config.render_colons, &line_index, it))
+        .map(|it| to_proto::inlay_hint(&line_index, it))
         .collect())
 }
 
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 2e131eeac9..846f151304 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -255,7 +255,7 @@ impl InlayHintKind {
 #[derive(Debug, Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct InlayHint {
-    pub label: InlayHintLabel,
+    pub label: String,
     pub position: Position,
     pub kind: Option<InlayHintKind>,
     pub tooltip: Option<String>,
@@ -263,24 +263,6 @@ pub struct InlayHint {
     pub padding_right: Option<bool>,
 }
 
-#[derive(Debug, Deserialize, Serialize)]
-#[serde(untagged)]
-pub enum InlayHintLabel {
-    String(String),
-    Parts(Vec<InlayHintLabelPart>),
-}
-
-#[derive(Debug, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct InlayHintLabelPart {
-    pub value: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub tooltip: Option<String>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub location: Option<lsp_types::LocationLink>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub command: Option<lsp_types::Command>,
-}
 pub enum Ssr {}
 
 impl Request for Ssr {
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index fd6602624f..7809d24dd6 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -413,17 +413,13 @@ pub(crate) fn signature_help(
     }
 }
 
-pub(crate) fn inlay_hint(
-    render_colons: bool,
-    line_index: &LineIndex,
-    inlay_hint: InlayHint,
-) -> lsp_ext::InlayHint {
+pub(crate) fn inlay_hint(line_index: &LineIndex, inlay_hint: InlayHint) -> lsp_ext::InlayHint {
     lsp_ext::InlayHint {
-        label: lsp_ext::InlayHintLabel::String(match inlay_hint.kind {
-            InlayKind::ParameterHint if render_colons => format!("{}:", inlay_hint.label),
-            InlayKind::TypeHint if render_colons => format!(": {}", inlay_hint.label),
-            _ => inlay_hint.label.to_string(),
-        }),
+        label: match inlay_hint.kind {
+            InlayKind::ParameterHint => format!("{}:", inlay_hint.label),
+            InlayKind::TypeHint => format!(": {}", inlay_hint.label),
+            InlayKind::ChainingHint => inlay_hint.label.to_string(),
+        },
         position: match inlay_hint.kind {
             InlayKind::ParameterHint => position(line_index, inlay_hint.range.start()),
             InlayKind::TypeHint | InlayKind::ChainingHint => {
@@ -437,12 +433,14 @@ pub(crate) fn inlay_hint(
         },
         tooltip: None,
         padding_left: Some(match inlay_hint.kind {
-            InlayKind::TypeHint | InlayKind::ParameterHint => false,
+            InlayKind::TypeHint => false,
+            InlayKind::ParameterHint => false,
             InlayKind::ChainingHint => true,
         }),
         padding_right: Some(match inlay_hint.kind {
-            InlayKind::TypeHint | InlayKind::ChainingHint => false,
+            InlayKind::TypeHint => false,
             InlayKind::ParameterHint => true,
+            InlayKind::ChainingHint => false,
         }),
     }
 }
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 3091bdcbf0..9f1c7fe0a3 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp_ext.rs hash: 854109e98d02a780
+lsp_ext.rs hash: e32fdde032ff6ebc
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue:
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index dd819abdd2..7e6c8225b1 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -347,11 +347,6 @@ Whether to show `References` action. Only applies when
 Whether to show `Run` action. Only applies when
 `#rust-analyzer.hoverActions.enable#` is set.
 --
-[[rust-analyzer.inlayHints.renderColons]]rust-analyzer.inlayHints.renderColons (default: `true`)::
-+
---
-Whether to render trailing colons for parameter hints, and trailing colons for parameter hints.
---
 [[rust-analyzer.inlayHints.chainingHints]]rust-analyzer.inlayHints.chainingHints (default: `true`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 6ccd6af80c..c94a09f084 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -766,11 +766,6 @@
                     "default": true,
                     "type": "boolean"
                 },
-                "rust-analyzer.inlayHints.renderColons": {
-                    "markdownDescription": "Whether to render trailing colons for parameter hints, and trailing colons for parameter hints.",
-                    "default": true,
-                    "type": "boolean"
-                },
                 "rust-analyzer.inlayHints.chainingHints": {
                     "markdownDescription": "Whether to show inlay type hints for method chains.",
                     "default": true,


Test patch:
diff --git a/crates/ide_completion/src/tests.rs b/crates/ide_completion/src/tests.rs
index f505e82d22..eedc37e2a6 100644
--- a/crates/ide_completion/src/tests.rs
+++ b/crates/ide_completion/src/tests.rs
@@ -154,12 +154,10 @@ fn render_completion_list(completions: Vec<CompletionItem>) -> String {
         .collect()
 }
 
-#[track_caller]
 pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
     check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
 }
 
-#[track_caller]
 pub(crate) fn check_edit_with_config(
     config: CompletionConfig,
     what: &str,
@@ -201,14 +199,32 @@ pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxE
     assert!(check(NodeOrToken::Token(token)));
 }
 
+pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
+    let (db, pos) = position(code);
+    let sema = Semantics::new(&db);
+    let original_file = sema.parse(pos.file_id);
+    let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
+    assert!(!check(NodeOrToken::Token(token)));
+}
+
 pub(crate) fn get_all_items(config: CompletionConfig, code: &str) -> Vec<CompletionItem> {
     let (db, position) = position(code);
     crate::completions(&db, &config, position).map_or_else(Vec::default, Into::into)
 }
 
+fn check_no_completion(ra_fixture: &str) {
+    let (db, position) = position(ra_fixture);
+
+    assert!(
+        crate::completions(&db, &TEST_CONFIG, position).is_none(),
+        "Completions were generated, but weren't expected"
+    );
+}
+
 #[test]
 fn test_no_completions_required() {
-    assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new());
+    cov_mark::check!(no_completion_required);
+    check_no_completion(r#"fn foo() { for i i$0 }"#);
 }
 
 #[test]
diff --git a/crates/ide_completion/src/tests/expression.rs b/crates/ide_completion/src/tests/expression.rs
index a841605e49..bb8b34b79a 100644
--- a/crates/ide_completion/src/tests/expression.rs
+++ b/crates/ide_completion/src/tests/expression.rs
@@ -61,7 +61,7 @@ fn baz() {
             fn function()    fn()
             sc STATIC
             un Union
-            ev TupleV(…)     (u32)
+            ev TupleV(…)     TupleV(u32)
             ct CONST
         "#]],
     )
@@ -171,7 +171,7 @@ impl Unit {
             fn function()   fn()
             sc STATIC
             un Union
-            ev TupleV(…)    (u32)
+            ev TupleV(…)    TupleV(u32)
             ct CONST
         "#]],
     );
@@ -200,7 +200,7 @@ impl Unit {
             fn function() fn()
             sc STATIC
             un Union
-            ev TupleV(…)  (u32)
+            ev TupleV(…)  TupleV(u32)
             ct CONST
         "#]],
     );
@@ -543,9 +543,9 @@ fn func() {
 }
 "#,
         expect![[r#"
-            ev TupleV(…)   (u32)
-            ev RecordV     {field: u32}
-            ev UnitV       ()
+            ev TupleV(…)   TupleV(u32)
+            ev RecordV {…} RecordV { field: u32 }
+            ev UnitV       UnitV
             ct ASSOC_CONST const ASSOC_CONST: ()
             fn assoc_fn()  fn()
             ta AssocType   type AssocType = ()
diff --git a/crates/ide_completion/src/tests/pattern.rs b/crates/ide_completion/src/tests/pattern.rs
index 0ca20f93b5..7767f24632 100644
--- a/crates/ide_completion/src/tests/pattern.rs
+++ b/crates/ide_completion/src/tests/pattern.rs
@@ -218,7 +218,7 @@ fn foo() {
         expect![[r#"
             kw ref
             kw mut
-            ev E::X  ()
+            ev E::X  E::X
             en E
             ma m!(…) macro_rules! m
         "#]],
@@ -291,9 +291,9 @@ fn func() {
 }
 "#,
         expect![[r#"
-            ev TupleV(…) (u32)
-            ev RecordV   {field: u32}
-            ev UnitV     ()
+            ev TupleV(…)   TupleV(u32)
+            ev RecordV {…} RecordV { field: u32 }
+            ev UnitV       UnitV
         "#]],
     );
 }
diff --git a/crates/ide_completion/src/tests/record.rs b/crates/ide_completion/src/tests/record.rs
index 3bb332b437..87d0d853b6 100644
--- a/crates/ide_completion/src/tests/record.rs
+++ b/crates/ide_completion/src/tests/record.rs
@@ -166,7 +166,7 @@ fn main() {
             kw true
             kw false
             kw return
-            sn Foo {…}              Foo { foo1: ${1:()}, foo2: ${2:()} }$0
+            sn Foo {…}              Foo { foo1: u32, foo2: u32 }
             fd ..Default::default()
             fd foo1                 u32
             fd foo2                 u32
diff --git a/crates/ide_completion/src/tests/use_tree.rs b/crates/ide_completion/src/tests/use_tree.rs
index 73cb83957f..ca06cc376f 100644
--- a/crates/ide_completion/src/tests/use_tree.rs
+++ b/crates/ide_completion/src/tests/use_tree.rs
@@ -167,7 +167,7 @@ impl Foo {
 }
 "#,
         expect![[r#"
-            ev Variant ()
+            ev Variant Variant
         "#]],
     );
 }
diff --git a/crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rast b/crates/parser/test_data/parser/inline/ok/0165_const_param_defaults.rast
similarity index 81%
rename from crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rast
rename to crates/parser/test_data/parser/inline/ok/0165_const_param_defaults.rast
index 8e52313651..6de10353bf 100644
--- a/crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rast
+++ b/crates/parser/test_data/parser/inline/ok/0165_const_param_defaults.rast
@@ -21,10 +21,11 @@ SOURCE_FILE
         WHITESPACE " "
         EQ "="
         WHITESPACE " "
-        PREFIX_EXPR
-          MINUS "-"
-          LITERAL
-            INT_NUMBER "1"
+        CONST_ARG
+          PREFIX_EXPR
+            MINUS "-"
+            LITERAL
+              INT_NUMBER "1"
       R_ANGLE ">"
     SEMICOLON ";"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rs b/crates/parser/test_data/parser/inline/ok/0165_const_param_defaults.rs
similarity index 100%
rename from crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rs
rename to crates/parser/test_data/parser/inline/ok/0165_const_param_defaults.rs
diff --git a/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rast b/crates/parser/test_data/parser/inline/ok/0188_const_arg_path.rast
similarity index 67%
rename from crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rast
rename to crates/parser/test_data/parser/inline/ok/0188_const_arg_path.rast
index 11002bf98d..440dfb06a3 100644
--- a/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rast
+++ b/crates/parser/test_data/parser/inline/ok/0188_const_arg_path.rast
@@ -3,7 +3,7 @@ SOURCE_FILE
     STRUCT_KW "struct"
     WHITESPACE " "
     NAME
-      IDENT "A"
+      IDENT "S"
     GENERIC_PARAM_LIST
       L_ANGLE "<"
       CONST_PARAM
@@ -17,20 +17,21 @@ SOURCE_FILE
           PATH
             PATH_SEGMENT
               NAME_REF
-                IDENT "i32"
+                IDENT "u32"
         WHITESPACE " "
         EQ "="
         WHITESPACE " "
-        PATH_EXPR
-          PATH
+        CONST_ARG
+          PATH_EXPR
             PATH
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "u32"
+              COLON2 "::"
               PATH_SEGMENT
                 NAME_REF
-                  IDENT "i32"
-            COLON2 "::"
-            PATH_SEGMENT
-              NAME_REF
-                IDENT "MAX"
+                  IDENT "MAX"
       R_ANGLE ">"
     SEMICOLON ";"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0188_const_arg_path.rs b/crates/parser/test_data/parser/inline/ok/0188_const_arg_path.rs
new file mode 100644
index 0000000000..ee075f3e50
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0188_const_arg_path.rs
@@ -0,0 +1,1 @@
+struct S<const N: u32 = u32::MAX>;
diff --git a/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rs b/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rs
deleted file mode 100644
index f3da43ca06..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rs
+++ /dev/null
@@ -1,1 +0,0 @@
-struct A<const N: i32 = i32::MAX>;
diff --git a/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rast b/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rast
deleted file mode 100644
index 0607ff54fb..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rast
+++ /dev/null
@@ -1,34 +0,0 @@
-SOURCE_FILE
-  STRUCT
-    STRUCT_KW "struct"
-    WHITESPACE " "
-    NAME
-      IDENT "A"
-    GENERIC_PARAM_LIST
-      L_ANGLE "<"
-      CONST_PARAM
-        CONST_KW "const"
-        WHITESPACE " "
-        NAME
-          IDENT "N"
-        COLON ":"
-        WHITESPACE " "
-        PATH_TYPE
-          PATH
-            PATH_SEGMENT
-              NAME_REF
-                IDENT "i32"
-        WHITESPACE " "
-        EQ "="
-        WHITESPACE " "
-        BLOCK_EXPR
-          STMT_LIST
-            L_CURLY "{"
-            WHITESPACE " "
-            LITERAL
-              INT_NUMBER "1"
-            WHITESPACE " "
-            R_CURLY "}"
-      R_ANGLE ">"
-    SEMICOLON ";"
-  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rs b/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rs
deleted file mode 100644
index 551bde0b00..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rs
+++ /dev/null
@@ -1,1 +0,0 @@
-struct A<const N: i32 = { 1 }>;
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index d811331ae3..871ff20a9b 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -440,7 +440,6 @@ pub fn bench(label: &'static str) -> impl Drop {
 
 /// Checks that the `file` has the specified `contents`. If that is not the
 /// case, updates the file and then fails the test.
-#[track_caller]
 pub fn ensure_file_contents(file: &Path, contents: &str) {
     if let Err(()) = try_ensure_file_contents(file, contents) {
         panic!("Some files were not up-to-date");