Repository: rust-analyzer/rust-analyzer
Pull request #11690 (2022-03-12 13:58:30)

feat: Add an assist for inlining type aliases
I'm working towards implementing #10881, but I'd like to get this in first with earlier feedback.

Is `inline_type_alias` a good enough name? I guess the follow up assist would be called `inline_type_alias_into_all_users` based on that.

![valid_inlines](https://user-images.githubusercontent.com/1277939/158020510-fed78b5c-4c7e-46d1-9151-3044a29b9990.gif)

![invalid_inlines](https://user-images.githubusercontent.com/1277939/158020516-8a2deb6d-c6ec-4adf-a15b-c514fc97dc43.gif)




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/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index e5d4ed8b92..0119ba5cb7 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -7,7 +7,6 @@ mod highlight;
 mod format;
 mod macro_;
 mod inject;
-mod escape;
 
 mod html;
 #[cfg(test)]
@@ -17,13 +16,16 @@ use hir::{InFile, Name, Semantics};
 use ide_db::RootDatabase;
 use rustc_hash::FxHashMap;
 use syntax::{
-    ast, AstNode, AstToken, NodeOrToken, SyntaxKind::*, SyntaxNode, TextRange, WalkEvent, T,
+    ast::{self, IsString},
+    AstNode, AstToken, NodeOrToken,
+    SyntaxKind::*,
+    SyntaxNode, TextRange, WalkEvent, T,
 };
 
 use crate::{
     syntax_highlighting::{
-        escape::highlight_escape_string, format::highlight_format_string, highlights::Highlights,
-        macro_::MacroHighlighter, tags::Highlight,
+        format::highlight_format_string, highlights::Highlights, macro_::MacroHighlighter,
+        tags::Highlight,
     },
     FileId, HlMod, HlTag,
 };
@@ -369,25 +371,29 @@ fn traverse(
         // string highlight injections, note this does not use the descended element as proc-macros
         // can rewrite string literals which invalidates our indices
         if let (Some(token), Some(descended_token)) = (token, descended_element.as_token()) {
-            if ast::String::can_cast(token.kind()) && ast::String::can_cast(descended_token.kind())
-            {
-                let string = ast::String::cast(token);
-                let string_to_highlight = ast::String::cast(descended_token.clone());
-                if let Some((string, expanded_string)) = string.zip(string_to_highlight) {
-                    if string.is_raw() {
-                        if inject::ra_fixture(hl, sema, &string, &expanded_string).is_some() {
-                            continue;
-                        }
+            let string = ast::String::cast(token);
+            let string_to_highlight = ast::String::cast(descended_token.clone());
+            if let Some((string, expanded_string)) = string.zip(string_to_highlight) {
+                if string.is_raw() {
+                    if inject::ra_fixture(hl, sema, &string, &expanded_string).is_some() {
+                        continue;
                     }
-                    highlight_format_string(hl, &string, &expanded_string, range);
-                    highlight_escape_string(hl, &string, range.start());
-                }
-            } else if ast::ByteString::can_cast(token.kind())
-                && ast::ByteString::can_cast(descended_token.kind())
-            {
-                if let Some(byte_string) = ast::ByteString::cast(token) {
-                    highlight_escape_string(hl, &byte_string, range.start());
                 }
+                highlight_format_string(hl, &string, &expanded_string, range);
+                // Highlight escape sequences
+                string.escaped_char_ranges(&mut |piece_range, char| {
+                    if char.is_err() {
+                        return;
+                    }
+
+                    if string.text()[piece_range.start().into()..].starts_with('\\') {
+                        hl.add(HlRange {
+                            range: piece_range + range.start(),
+                            highlight: HlTag::EscapeSequence.into(),
+                            binding_hash: None,
+                        });
+                    }
+                });
             }
         }
 
diff --git a/crates/ide/src/syntax_highlighting/escape.rs b/crates/ide/src/syntax_highlighting/escape.rs
deleted file mode 100644
index 6a1236c793..0000000000
--- a/crates/ide/src/syntax_highlighting/escape.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-//! Syntax highlighting for escape sequences
-use crate::syntax_highlighting::highlights::Highlights;
-use crate::{HlRange, HlTag};
-use syntax::ast::IsString;
-use syntax::TextSize;
-
-pub(super) fn highlight_escape_string<T: IsString>(
-    stack: &mut Highlights,
-    string: &T,
-    start: TextSize,
-) {
-    string.escaped_char_ranges(&mut |piece_range, char| {
-        if char.is_err() {
-            return;
-        }
-
-        if string.text()[piece_range.start().into()..].starts_with('\\') {
-            stack.add(HlRange {
-                range: piece_range + start,
-                highlight: HlTag::EscapeSequence.into(),
-                binding_hash: None,
-            });
-        }
-    });
-}
diff --git a/crates/ide_assists/src/handlers/inline_type_alias.rs b/crates/ide_assists/src/handlers/inline_type_alias.rs
new file mode 100644
index 0000000000..eeb2e2e667
--- /dev/null
+++ b/crates/ide_assists/src/handlers/inline_type_alias.rs
@@ -0,0 +1,758 @@
+// Some ideas for future improvements:
+// - Support replacing aliases which are used in expressions, e.g. `A::new()`.
+// - "inline_alias_to_users" assist #10881.
+// - Remove unused aliases if there are no longer any users, see inline_call.rs.
+
+use hir::PathResolution;
+use itertools::Itertools;
+use std::collections::HashMap;
+use syntax::{
+    ast::{
+        self,
+        make::{self},
+        HasGenericParams, HasName,
+    },
+    ted::{self},
+    AstNode, NodeOrToken, SyntaxNode,
+};
+
+use crate::{
+    assist_context::{AssistContext, Assists},
+    AssistId, AssistKind,
+};
+
+// Assist: inline_type_alias
+//
+// Replace a type alias with its concrete type.
+//
+// ```
+// type A<T = u32> = Vec<T>;
+//
+// fn main() {
+//     let a: $0A;
+// }
+// ```
+// ->
+// ```
+// type A<T = u32> = Vec<T>;
+//
+// fn main() {
+//     let a: Vec<u32>;
+// }
+// ```
+pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let alias_instance = ctx.find_node_at_offset::<ast::PathType>()?;
+    let alias = get_type_alias(&ctx, &alias_instance)?;
+    let concrete_type = alias.ty()?;
+
+    enum Replacement {
+        Generic { lifetime_map: LifetimeMap, const_and_type_map: ConstAndTypeMap },
+        Plain,
+    }
+
+    let replacement = if let Some(alias_generics) = alias.generic_param_list() {
+        if alias_generics.generic_params().next().is_none() {
+            cov_mark::hit!(no_generics_params);
+            return None;
+        }
+
+        let instance_args =
+            alias_instance.syntax().descendants().find_map(ast::GenericArgList::cast);
+
+        Replacement::Generic {
+            lifetime_map: LifetimeMap::new(&instance_args, &alias_generics)?,
+            const_and_type_map: ConstAndTypeMap::new(&instance_args, &alias_generics)?,
+        }
+    } else {
+        Replacement::Plain
+    };
+
+    let target = alias_instance.syntax().text_range();
+
+    acc.add(
+        AssistId("inline_type_alias", AssistKind::RefactorInline),
+        "Inline type alias",
+        target,
+        |builder| {
+            let replacement_text = match replacement {
+                Replacement::Generic { lifetime_map, const_and_type_map } => {
+                    create_replacement(&lifetime_map, &const_and_type_map, &concrete_type)
+                }
+                Replacement::Plain => concrete_type.to_string(),
+            };
+
+            builder.replace(target, replacement_text);
+        },
+    )
+}
+
+struct LifetimeMap(HashMap<String, ast::Lifetime>);
+
+impl LifetimeMap {
+    fn new(
+        instance_args: &Option<ast::GenericArgList>,
+        alias_generics: &ast::GenericParamList,
+    ) -> Option<Self> {
+        let mut inner = HashMap::new();
+
+        let wildcard_lifetime = make::lifetime("'_");
+        let lifetimes = alias_generics
+            .lifetime_params()
+            .filter_map(|lp| lp.lifetime())
+            .map(|l| l.to_string())
+            .collect_vec();
+
+        for lifetime in &lifetimes {
+            inner.insert(lifetime.to_string(), wildcard_lifetime.clone());
+        }
+
+        if let Some(instance_generic_args_list) = &instance_args {
+            for (index, lifetime) in instance_generic_args_list
+                .lifetime_args()
+                .filter_map(|arg| arg.lifetime())
+                .enumerate()
+            {
+                let key = match lifetimes.get(index) {
+                    Some(key) => key,
+                    None => {
+                        cov_mark::hit!(too_many_lifetimes);
+                        return None;
+                    }
+                };
+
+                inner.insert(key.clone(), lifetime);
+            }
+        }
+
+        Some(Self(inner))
+    }
+}
+
+struct ConstAndTypeMap(HashMap<String, SyntaxNode>);
+
+impl ConstAndTypeMap {
+    fn new(
+        instance_args: &Option<ast::GenericArgList>,
+        alias_generics: &ast::GenericParamList,
+    ) -> Option<Self> {
+        let mut inner = HashMap::new();
+        let instance_generics = generic_args_to_const_and_type_generics(instance_args);
+        let alias_generics = generic_param_list_to_const_and_type_generics(&alias_generics);
+
+        if instance_generics.len() > alias_generics.len() {
+            cov_mark::hit!(too_many_generic_args);
+            return None;
+        }
+
+        // Any declaration generics that don't have a default value must have one
+        // provided by the instance.
+        for (i, declaration_generic) in alias_generics.iter().enumerate() {
+            let key = declaration_generic.replacement_key()?;
+
+            if let Some(instance_generic) = instance_generics.get(i) {
+                inner.insert(key, instance_generic.replacement_value()?);
+            } else if let Some(value) = declaration_generic.replacement_value() {
+                inner.insert(key, value);
+            } else {
+                cov_mark::hit!(missing_replacement_param);
+                return None;
+            }
+        }
+
+        Some(Self(inner))
+    }
+}
+
+/// This doesn't attempt to ensure specified generics are compatible with those
+/// required by the type alias, other than lifetimes which must either all be
+/// specified or all omitted. It will replace TypeArgs with ConstArgs and vice
+/// versa if they're in the wrong position. It supports partially specified
+/// generics.
+///
+/// 1. Map the provided instance's generic args to the type alias's generic
+///    params:
+///
+///    ```
+///    type A<'a, const N: usize, T = u64> = &'a [T; N];
+///          ^ alias generic params
+///    let a: A<100>;
+///            ^ instance generic args
+///    ```
+///
+///    generic['a] = '_ due to omission
+///    generic[N] = 100 due to the instance arg
+///    generic[T] = u64 due to the default param
+///
+/// 2. Copy the concrete type and substitute in each found mapping:
+///
+///    &'_ [u64; 100]
+///
+/// 3. Remove wildcard lifetimes entirely:
+///
+///    &[u64; 100]
+fn create_replacement(
+    lifetime_map: &LifetimeMap,
+    const_and_type_map: &ConstAndTypeMap,
+    concrete_type: &ast::Type,
+) -> String {
+    let updated_concrete_type = concrete_type.clone_for_update();
+    let mut replacements = Vec::new();
+    let mut removals = Vec::new();
+
+    for syntax in updated_concrete_type.syntax().descendants() {
+        let syntax_string = syntax.to_string();
+        let syntax_str = syntax_string.as_str();
+
+        if let Some(old_lifetime) = ast::Lifetime::cast(syntax.clone()) {
+            if let Some(new_lifetime) = lifetime_map.0.get(&old_lifetime.to_string()) {
+                if new_lifetime.text() == "'_" {
+                    removals.push(NodeOrToken::Node(syntax.clone()));
+
+                    if let Some(ws) = syntax.next_sibling_or_token() {
+                        removals.push(ws.clone());
+                    }
+
+                    continue;
+                }
+
+                replacements.push((syntax.clone(), new_lifetime.syntax().clone_for_update()));
+            }
+        } else if let Some(replacement_syntax) = const_and_type_map.0.get(syntax_str) {
+            let new_string = replacement_syntax.to_string();
+            let new = if new_string == "_" {
+                make::wildcard_pat().syntax().clone_for_update()
+            } else {
+                replacement_syntax.clone_for_update()
+            };
+
+            replacements.push((syntax.clone(), new));
+        }
+    }
+
+    for (old, new) in replacements {
+        ted::replace(old, new);
+    }
+
+    for syntax in removals {
+        ted::remove(syntax);
+    }
+
+    updated_concrete_type.to_string()
+}
+
+fn get_type_alias(ctx: &AssistContext, path: &ast::PathType) -> Option<ast::TypeAlias> {
+    let resolved_path = ctx.sema.resolve_path(&path.path()?)?;
+
+    // We need the generics in the correct order to be able to map any provided
+    // instance generics to declaration generics. The `hir::TypeAlias` doesn't
+    // keep the order, so we must get the `ast::TypeAlias` from the hir
+    // definition.
+    if let PathResolution::Def(hir::ModuleDef::TypeAlias(ta)) = resolved_path {
+        Some(ctx.sema.source(ta)?.value)
+    } else {
+        None
+    }
+}
+
+enum ConstOrTypeGeneric {
+    ConstArg(ast::ConstArg),
+    TypeArg(ast::TypeArg),
+    ConstParam(ast::ConstParam),
+    TypeParam(ast::TypeParam),
+}
+
+impl ConstOrTypeGeneric {
+    fn replacement_key(&self) -> Option<String> {
+        // Only params are used as replacement keys.
+        match self {
+            ConstOrTypeGeneric::ConstParam(cp) => Some(cp.name()?.to_string()),
+            ConstOrTypeGeneric::TypeParam(tp) => Some(tp.name()?.to_string()),
+            _ => None,
+        }
+    }
+
+    fn replacement_value(&self) -> Option<SyntaxNode> {
+        Some(match self {
+            ConstOrTypeGeneric::ConstArg(ca) => ca.expr()?.syntax().clone(),
+            ConstOrTypeGeneric::TypeArg(ta) => ta.syntax().clone(),
+            ConstOrTypeGeneric::ConstParam(cp) => cp.default_val()?.syntax().clone(),
+            ConstOrTypeGeneric::TypeParam(tp) => tp.default_type()?.syntax().clone(),
+        })
+    }
+}
+
+fn generic_param_list_to_const_and_type_generics(
+    generics: &ast::GenericParamList,
+) -> Vec<ConstOrTypeGeneric> {
+    let mut others = Vec::new();
+
+    for param in generics.generic_params() {
+        match param {
+            ast::GenericParam::LifetimeParam(_) => {}
+            ast::GenericParam::ConstParam(cp) => {
+                others.push(ConstOrTypeGeneric::ConstParam(cp));
+            }
+            ast::GenericParam::TypeParam(tp) => others.push(ConstOrTypeGeneric::TypeParam(tp)),
+        }
+    }
+
+    others
+}
+
+fn generic_args_to_const_and_type_generics(
+    generics: &Option<ast::GenericArgList>,
+) -> Vec<ConstOrTypeGeneric> {
+    let mut others = Vec::new();
+
+    // It's fine for there to be no instance generics because the declaration
+    // might have default values or they might be inferred.
+    if let Some(generics) = generics {
+        for arg in generics.generic_args() {
+            match arg {
+                ast::GenericArg::TypeArg(ta) => {
+                    others.push(ConstOrTypeGeneric::TypeArg(ta));
+                }
+                ast::GenericArg::ConstArg(ca) => {
+                    others.push(ConstOrTypeGeneric::ConstArg(ca));
+                }
+                _ => {}
+            }
+        }
+    }
+
+    others
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    #[test]
+    fn empty_generic_params() {
+        cov_mark::check!(no_generics_params);
+        check_assist_not_applicable(
+            inline_type_alias,
+            r#"
+type A<> = T;
+fn main() {
+    let a: $0A<u32>;
+}
+            "#,
+        );
+    }
+
+    #[test]
+    fn too_many_generic_args() {
+        cov_mark::check!(too_many_generic_args);
+        check_assist_not_applicable(
+            inline_type_alias,
+            r#"
+type A<T> = T;
+fn main() {
+    let a: $0A<u32, u64>;
+}
+            "#,
+        );
+    }
+
+    #[test]
+    fn too_many_lifetimes() {
+        cov_mark::check!(too_many_lifetimes);
+        check_assist_not_applicable(
+            inline_type_alias,
+            r#"
+type A<'a> = &'a &'b u32;
+fn f<'a>() {
+    let a: $0A<'a, 'b> = 0;
+}
+"#,
+        );
+    }
+
+    // This must be supported in order to support "inline_alias_to_users" or
+    // whatever it will be called.
+    #[test]
+    fn alias_as_expression_ignored() {
+        check_assist_not_applicable(
+            inline_type_alias,
+            r#"
+type A = Vec<u32>;
+fn main() {
+    let a: A = $0A::new();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn primitive_arg() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A<T> = T;
+fn main() {
+    let a: $0A<u32> = 0;
+}
+"#,
+            r#"
+type A<T> = T;
+fn main() {
+    let a: u32 = 0;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn no_generic_replacements() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A = Vec<u32>;
+fn main() {
+    let a: $0A;
+}
+"#,
+            r#"
+type A = Vec<u32>;
+fn main() {
+    let a: Vec<u32>;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn param_expression() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A<const N: usize = { 1 }> = [u32; N];
+fn main() {
+    let a: $0A;
+}
+"#,
+            r#"
+type A<const N: usize = { 1 }> = [u32; N];
+fn main() {
+    let a: [u32; { 1 }];
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn param_default_value() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A<const N: usize = 1> = [u32; N];
+fn main() {
+    let a: $0A;
+}
+"#,
+            r#"
+type A<const N: usize = 1> = [u32; N];
+fn main() {
+    let a: [u32; 1];
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn all_param_types() {
+        check_assist(
+            inline_type_alias,
+            r#"
+struct Struct<const C: usize>;
+type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
+fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
+    let a: $0A<'inner2, 'outer2, Outer2, INNER2, Inner2, OUTER2>;
+}
+"#,
+            r#"
+struct Struct<const C: usize>;
+type A<'inner1, 'outer1, Outer1, const INNER1: usize, Inner1: Clone, const OUTER1: usize> = (Struct<INNER1>, Struct<OUTER1>, Outer1, &'inner1 (), Inner1, &'outer1 ());
+fn foo<'inner2, 'outer2, Outer2, const INNER2: usize, Inner2, const OUTER2: usize>() {
+    let a: (Struct<INNER2>, Struct<OUTER2>, Outer2, &'inner2 (), Inner2, &'outer2 ());
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn omitted_lifetimes() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A<'l, 'r> = &'l &'r u32;
+fn main() {
+    let a: $0A;
+}
+"#,
+            r#"
+type A<'l, 'r> = &'l &'r u32;
+fn main() {
+    let a: &&u32;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn omitted_type() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
+fn main() {
+    let a: $0A<'_, '_>;
+}
+"#,
+            r#"
+type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
+fn main() {
+    let a: &std::collections::HashMap<&str, u32>;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn omitted_everything() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
+fn main() {
+    let v = std::collections::HashMap<&str, u32>;
+    let a: $0A = &v;
+}
+"#,
+            r#"
+type A<'r, 'l, T = u32> = &'l std::collections::HashMap<&'r str, T>;
+fn main() {
+    let v = std::collections::HashMap<&str, u32>;
+    let a: &std::collections::HashMap<&str, u32> = &v;
+}
+"#,
+        );
+    }
+
+    // This doesn't actually cause the GenericArgsList to contain a AssocTypeArg.
+    #[test]
+    fn arg_associated_type() {
+        check_assist(
+            inline_type_alias,
+            r#"
+trait Tra { type Assoc; fn a(); }
+struct Str {}
+impl Tra for Str {
+    type Assoc = u32;
+    fn a() {
+        type A<T> = Vec<T>;
+        let a: $0A<Self::Assoc>;
+    }
+}
+"#,
+            r#"
+trait Tra { type Assoc; fn a(); }
+struct Str {}
+impl Tra for Str {
+    type Assoc = u32;
+    fn a() {
+        type A<T> = Vec<T>;
+        let a: Vec<Self::Assoc>;
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn param_default_associated_type() {
+        check_assist(
+            inline_type_alias,
+            r#"
+trait Tra { type Assoc; fn a() }
+struct Str {}
+impl Tra for Str {
+    type Assoc = u32;
+    fn a() {
+        type A<T = Self::Assoc> = Vec<T>;
+        let a: $0A;
+    }
+}
+"#,
+            r#"
+trait Tra { type Assoc; fn a() }
+struct Str {}
+impl Tra for Str {
+    type Assoc = u32;
+    fn a() {
+        type A<T = Self::Assoc> = Vec<T>;
+        let a: Vec<Self::Assoc>;
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn function_pointer() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A = fn(u32);
+fn foo(a: u32) {}
+fn main() {
+    let a: $0A = foo;
+}
+"#,
+            r#"
+type A = fn(u32);
+fn foo(a: u32) {}
+fn main() {
+    let a: fn(u32) = foo;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn closure() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A = Box<dyn FnOnce(u32) -> u32>;
+fn main() {
+    let a: $0A = Box::new(|_| 0);
+}
+"#,
+            r#"
+type A = Box<dyn FnOnce(u32) -> u32>;
+fn main() {
+    let a: Box<dyn FnOnce(u32) -> u32> = Box::new(|_| 0);
+}
+"#,
+        );
+    }
+
+    // Type aliases can't be used in traits, but someone might use the assist to
+    // fix the error.
+    #[test]
+    fn bounds() {
+        check_assist(
+            inline_type_alias,
+            r#"type A = std::io::Write; fn f<T>() where T: $0A {}"#,
+            r#"type A = std::io::Write; fn f<T>() where T: std::io::Write {}"#,
+        );
+    }
+
+    #[test]
+    fn function_parameter() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A = std::io::Write;
+fn f(a: impl $0A) {}
+"#,
+            r#"
+type A = std::io::Write;
+fn f(a: impl std::io::Write) {}
+"#,
+        );
+    }
+
+    #[test]
+    fn arg_expression() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A<const N: usize> = [u32; N];
+fn main() {
+    let a: $0A<{ 1 + 1 }>;
+}
+"#,
+            r#"
+type A<const N: usize> = [u32; N];
+fn main() {
+    let a: [u32; { 1 + 1 }];
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn alias_instance_generic_path() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A<const N: usize> = [u32; N];
+fn main() {
+    let a: $0A<u32::MAX>;
+}
+"#,
+            r#"
+type A<const N: usize> = [u32; N];
+fn main() {
+    let a: [u32; u32::MAX];
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn generic_type() {
+        check_assist(
+            inline_type_alias,
+            r#"
+type A = String;
+fn f(a: Vec<$0A>) {}
+"#,
+            r#"
+type A = String;
+fn f(a: Vec<String>) {}
+"#,
+        );
+    }
+
+    #[test]
+    fn missing_replacement_param() {
+        cov_mark::check!(missing_replacement_param);
+        check_assist_not_applicable(
+            inline_type_alias,
+            r#"
+type A<U> = Vec<T>;
+fn main() {
+    let a: $0A;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn full_path_type_is_replaced() {
+        check_assist(
+            inline_type_alias,
+            r#"
+mod foo {
+    pub type A = String;
+}
+fn main() {
+    let a: foo::$0A;
+}
+"#,
+            r#"
+mod foo {
+    pub type A = String;
+}
+fn main() {
+    let a: String;
+}
+"#,
+        );
+    }
+}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 067f4d8e14..6eff8871e8 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -42,7 +42,7 @@
 //!   useful and (worse) less predictable. The user should have a clear
 //!   intuition when each particular assist is available.
 //! * Make small assists, which compose. Example: rather than auto-importing
-//!   enums in `fill_match_arms`, we use fully-qualified names. There's a
+//!   enums in `add_missing_match_arms`, we use fully-qualified names. There's a
 //!   separate assist to shorten a fully-qualified name.
 //! * Distinguish between assists and fixits for diagnostics. Internally, fixits
 //!   and assists are equivalent. They have the same "show a list + invoke a
@@ -150,6 +150,7 @@ mod handlers {
     mod add_return_type;
     mod inline_call;
     mod inline_local_variable;
+    mod inline_type_alias;
     mod introduce_named_lifetime;
     mod invert_if;
     mod merge_imports;
@@ -231,6 +232,7 @@ mod handlers {
             inline_call::inline_call,
             inline_call::inline_into_callers,
             inline_local_variable::inline_local_variable,
+            inline_type_alias::inline_type_alias,
             introduce_named_generic::introduce_named_generic,
             introduce_named_lifetime::introduce_named_lifetime,
             invert_if::invert_if,
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 86b1f534b2..acd02616b1 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    Bar
-                ev Baz    Baz
+                ev Bar    ()
+                ev Baz    ()
                 me foo(…) fn(self)
             "#]],
         );
@@ -626,7 +626,7 @@ fn main() {
 }
 "#,
             expect![[r#"
-                ev Bar Bar
+                ev 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 e8ebb3e337..e7a5426a26 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -8,7 +8,6 @@ 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;
 
@@ -429,14 +428,14 @@ fn main() { Foo::Fo$0 }
             expect![[r#"
                 [
                     CompletionItem {
-                        label: "Foo {…}",
+                        label: "Foo",
                         source_range: 54..56,
                         delete: 54..56,
-                        insert: "Foo { x: ${1:()}, y: ${2:()} }$0",
+                        insert: "Foo",
                         kind: SymbolKind(
                             Variant,
                         ),
-                        detail: "Foo { x: i32, y: i32 }",
+                        detail: "{x: i32, y: i32}",
                     },
                 ]
             "#]],
@@ -444,7 +443,7 @@ fn main() { Foo::Fo$0 }
     }
 
     #[test]
-    fn enum_detail_includes_tuple_fields() {
+    fn enum_detail_doesnt_include_tuple_fields() {
         check(
             r#"
 enum Foo { Foo (i32, i32) }
@@ -458,11 +457,13 @@ fn main() { Foo::Fo$0 }
                         label: "Foo(…)",
                         source_range: 46..48,
                         delete: 46..48,
-                        insert: "Foo(${1:()}, ${2:()})$0",
+                        insert: "Foo($0)",
                         kind: SymbolKind(
                             Variant,
                         ),
-                        detail: "Foo(i32, i32)",
+                        lookup: "Foo",
+                        detail: "(i32, i32)",
+                        trigger_call_info: true,
                     },
                 ]
             "#]],
@@ -509,7 +510,7 @@ fn main() { fo$0 }
     }
 
     #[test]
-    fn enum_detail_just_name_for_unit() {
+    fn enum_detail_just_parentheses_for_unit() {
         check(
             r#"
 enum Foo { Foo }
@@ -523,11 +524,11 @@ fn main() { Foo::Fo$0 }
                         label: "Foo",
                         source_range: 35..37,
                         delete: 35..37,
-                        insert: "Foo$0",
+                        insert: "Foo",
                         kind: SymbolKind(
                             Variant,
                         ),
-                        detail: "Foo",
+                        detail: "()",
                     },
                 ]
             "#]],
@@ -571,15 +572,15 @@ fn main() { let _: m::Spam = S$0 }
                         ),
                     },
                     CompletionItem {
-                        label: "m::Spam::Bar(…)",
+                        label: "Spam::Bar(…)",
                         source_range: 75..76,
                         delete: 75..76,
-                        insert: "m::Spam::Bar(${1:()})$0",
+                        insert: "Spam::Bar($0)",
                         kind: SymbolKind(
                             Variant,
                         ),
                         lookup: "Spam::Bar",
-                        detail: "m::Spam::Bar(i32)",
+                        detail: "(i32)",
                         relevance: CompletionRelevance {
                             exact_name_match: false,
                             type_match: Some(
@@ -590,17 +591,18 @@ 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$0",
+                        insert: "m::Spam::Foo",
                         kind: SymbolKind(
                             Variant,
                         ),
                         lookup: "Spam::Foo",
-                        detail: "m::Spam::Foo",
+                        detail: "()",
                         relevance: CompletionRelevance {
                             exact_name_match: false,
                             type_match: Some(
@@ -785,11 +787,11 @@ use self::E::*;
                         label: "V",
                         source_range: 10..12,
                         delete: 10..12,
-                        insert: "V$0",
+                        insert: "V",
                         kind: SymbolKind(
                             Variant,
                         ),
-                        detail: "V",
+                        detail: "()",
                         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 70767a2a9c..653515fec2 100644
--- a/crates/ide_completion/src/render/builder_ext.rs
+++ b/crates/ide_completion/src/render/builder_ext.rs
@@ -8,7 +8,6 @@ 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
deleted file mode 100644
index c7f3bd1f79..0000000000
--- a/crates/ide_completion/src/render/compound.rs
+++ /dev/null
@@ -1,95 +0,0 @@
-//! 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 5b485005d3..914ace910d 100644
--- a/crates/ide_completion/src/render/enum_variant.rs
+++ b/crates/ide_completion/src/render/enum_variant.rs
@@ -1,15 +1,13 @@
 //! Renderer for `enum` variants.
 
-use hir::{HasAttrs, StructKind};
+use hir::{db::HirDatabase, HasAttrs, HirDisplay, StructKind};
 use ide_db::SymbolKind;
+use itertools::Itertools;
 use syntax::SmolStr;
 
 use crate::{
     item::{CompletionItem, ImportEdit},
-    render::{
-        compound::{format_literal_label, render_record, render_tuple, RenderedCompound},
-        compute_ref_match, compute_type_match, RenderContext,
-    },
+    render::{builder_ext::Params, compute_ref_match, compute_type_match, RenderContext},
     CompletionRelevance,
 };
 
@@ -48,42 +46,20 @@ fn render(
     let qualified_name = qualified_name.to_string();
     let short_qualified_name: SmolStr = short_qualified_name.to_string().into();
 
-    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),
-    );
-
+    let mut item = CompletionItem::new(SymbolKind::Variant, ctx.source_range(), qualified_name);
     item.set_documentation(variant.docs(db))
         .set_deprecated(ctx.is_deprecated(variant))
-        .detail(rendered.detail);
-
-    match ctx.snippet_cap() {
-        Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal),
-        None => item.insert_text(rendered.literal),
-    };
+        .detail(detail(db, variant, variant_kind));
 
     if let Some(import_to_add) = import_to_add {
         item.add_import(import_to_add);
     }
 
-    if qualified {
+    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 {
         item.lookup_by(short_qualified_name);
     }
 
@@ -99,3 +75,50 @@ 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 a686be6691..3bc94fa782 100644
--- a/crates/ide_completion/src/render/struct_literal.rs
+++ b/crates/ide_completion/src/render/struct_literal.rs
@@ -1,15 +1,11 @@
 //! Renderer for `struct` literal.
 
-use hir::{HasAttrs, Name, StructKind};
+use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
+use ide_db::SnippetCap;
+use itertools::Itertools;
 use syntax::SmolStr;
 
-use crate::{
-    render::compound::{
-        format_literal_label, render_record, render_tuple, visible_fields, RenderedCompound,
-    },
-    render::RenderContext,
-    CompletionItem, CompletionItemKind,
-};
+use crate::{render::RenderContext, CompletionItem, CompletionItemKind};
 
 pub(crate) fn render_struct_literal(
     ctx: RenderContext<'_>,
@@ -29,31 +25,29 @@ pub(crate) fn render_struct_literal(
 
     let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_smol_str();
 
-    let rendered = render_literal(&ctx, path, &name, strukt.kind(ctx.db()), &visible_fields)?;
+    let literal = render_literal(&ctx, path, &name, strukt.kind(ctx.db()), &visible_fields)?;
 
-    Some(build_completion(&ctx, name, rendered, strukt.kind(ctx.db()), strukt))
+    Some(build_completion(ctx, name, literal, strukt))
 }
 
 fn build_completion(
-    ctx: &RenderContext<'_>,
+    ctx: RenderContext<'_>,
     name: SmolStr,
-    rendered: RenderedCompound,
-    kind: StructKind,
+    literal: String,
     def: impl HasAttrs + Copy,
 ) -> CompletionItem {
     let mut item = CompletionItem::new(
         CompletionItemKind::Snippet,
         ctx.source_range(),
-        format_literal_label(&name, kind),
+        SmolStr::from_iter([&name, " {…}"]),
     );
-
     item.set_documentation(ctx.docs(def))
         .set_deprecated(ctx.is_deprecated(def))
-        .detail(&rendered.detail)
+        .detail(&literal)
         .set_relevance(ctx.completion_relevance());
     match ctx.snippet_cap() {
-        Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal),
-        None => item.insert_text(rendered.literal),
+        Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
+        None => item.insert_text(literal),
     };
     item.build()
 }
@@ -64,7 +58,7 @@ fn render_literal(
     name: &str,
     kind: StructKind,
     fields: &[hir::Field],
-) -> Option<RenderedCompound> {
+) -> Option<String> {
     let path_string;
 
     let qualified_name = if let Some(path) = path {
@@ -74,18 +68,69 @@ fn render_literal(
         name
     };
 
-    let mut rendered = match kind {
+    let mut literal = match kind {
         StructKind::Tuple if ctx.snippet_cap().is_some() => {
-            render_tuple(ctx.db(), ctx.snippet_cap(), fields, Some(qualified_name))
+            render_tuple_as_literal(fields, qualified_name)
         }
         StructKind::Record => {
-            render_record(ctx.db(), ctx.snippet_cap(), fields, Some(qualified_name))
+            render_record_as_literal(ctx.db(), ctx.snippet_cap(), fields, qualified_name)
         }
         _ => return None,
     };
 
     if ctx.snippet_cap().is_some() {
-        rendered.literal.push_str("$0");
+        literal.push_str("$0");
     }
-    Some(rendered)
+    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))
 }
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 2faef70072..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,13 +433,14 @@ pub(crate) fn inlay_hint(
         },
         tooltip: None,
         padding_left: Some(match inlay_hint.kind {
-            InlayKind::TypeHint => !render_colons,
+            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/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 333ee35d63..e1d4addb52 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -764,6 +764,15 @@ impl ast::Meta {
     }
 }
 
+impl ast::GenericArgList {
+    pub fn lifetime_args(&self) -> impl Iterator<Item = ast::LifetimeArg> {
+        self.generic_args().filter_map(|arg| match arg {
+            ast::GenericArg::LifetimeArg(it) => Some(it),
+            _ => None,
+        })
+    }
+}
+
 impl ast::GenericParamList {
     pub fn lifetime_params(&self) -> impl Iterator<Item = ast::LifetimeParam> {
         self.generic_params().filter_map(|param| match param {
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/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index 3715164bbf..07c7881a1f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -136,9 +136,6 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal">World"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">\u{48}</span><span class="escape_sequence">\x65</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6F</span><span class="string_literal"> World"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 
-    <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x00</span><span class="escape_sequence">\x63</span><span class="escape_sequence">\n</span><span class="string_literal">"</span><span class="semicolon">;</span>
-    <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">b"</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x00</span><span class="escape_sequence">\x63</span><span class="escape_sequence">\n</span><span class="string_literal">"</span><span class="semicolon">;</span>
-
     <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> A <span class="operator">=</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> ничоси <span class="operator">=</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index a8c69875e0..efd03c2823 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -459,9 +459,6 @@ fn main() {
     println!("Hello\nWorld");
     println!("\u{48}\x65\x6C\x6C\x6F World");
 
-    let _ = "\x28\x28\x00\x63\n";
-    let _ = b"\x28\x28\x00\x63\n";
-
     println!("{\x41}", A = 92);
     println!("{ничоси}", ничоси = 92);
 
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 485b807d05..84343daa6a 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -1239,6 +1239,27 @@ fn main() {
     )
 }
 
+#[test]
+fn doctest_inline_type_alias() {
+    check_doc_test(
+        "inline_type_alias",
+        r#####"
+type A<T = u32> = Vec<T>;
+
+fn main() {
+    let a: $0A;
+}
+"#####,
+        r#####"
+type A<T = u32> = Vec<T>;
+
+fn main() {
+    let a: Vec<u32>;
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_introduce_named_generic() {
     check_doc_test(
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 bb8b34b79a..a841605e49 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(…)     TupleV(u32)
+            ev TupleV(…)     (u32)
             ct CONST
         "#]],
     )
@@ -171,7 +171,7 @@ impl Unit {
             fn function()   fn()
             sc STATIC
             un Union
-            ev TupleV(…)    TupleV(u32)
+            ev TupleV(…)    (u32)
             ct CONST
         "#]],
     );
@@ -200,7 +200,7 @@ impl Unit {
             fn function() fn()
             sc STATIC
             un Union
-            ev TupleV(…)  TupleV(u32)
+            ev TupleV(…)  (u32)
             ct CONST
         "#]],
     );
@@ -543,9 +543,9 @@ fn func() {
 }
 "#,
         expect![[r#"
-            ev TupleV(…)   TupleV(u32)
-            ev RecordV {…} RecordV { field: u32 }
-            ev UnitV       UnitV
+            ev TupleV(…)   (u32)
+            ev RecordV     {field: u32}
+            ev 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 7767f24632..0ca20f93b5 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  E::X
+            ev E::X  ()
             en E
             ma m!(…) macro_rules! m
         "#]],
@@ -291,9 +291,9 @@ fn func() {
 }
 "#,
         expect![[r#"
-            ev TupleV(…)   TupleV(u32)
-            ev RecordV {…} RecordV { field: u32 }
-            ev UnitV       UnitV
+            ev TupleV(…) (u32)
+            ev RecordV   {field: u32}
+            ev UnitV     ()
         "#]],
     );
 }
diff --git a/crates/ide_completion/src/tests/record.rs b/crates/ide_completion/src/tests/record.rs
index 87d0d853b6..3bb332b437 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: u32, foo2: u32 }
+            sn Foo {…}              Foo { foo1: ${1:()}, foo2: ${2:()} }$0
             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 ca06cc376f..73cb83957f 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 Variant
+            ev Variant ()
         "#]],
     );
 }
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");