Repository: rust-analyzer/rust-analyzer
Pull request #7691 (2021-02-16 03:10:06)

Suggest name in extract variable
Generate better default name in extract variable assist as was mentioned in issue #1587

# Currently supported
(in order of declining precedence)
1. Expr is argument to a function; use corresponding parameter name
2. Expr is result of a function or method call; use this function/method's name
3. Use expr type name (if possible)
4. Fallback to `var_name` otherwise

# Showcase

![generate_derive_variable_name_from_method](https://user-images.githubusercontent.com/4218373/108013304-72105400-701c-11eb-9f13-eec52e74d0cc.gif)
![generate_derive_variable_name_from_param](https://user-images.githubusercontent.com/4218373/108013305-72a8ea80-701c-11eb-957e-2214f7f005de.gif)

# Questions

* Should we more aggressively strip known types? E.g. we already strip `&T -> T`; should we strip `Option<T> -> T`, `Result<T, E> -> T`, and others?
* Integers and floats use `var_name` by default. Should we introduce a name, like `i`, `f` etc?
* Can we return a list and suggest a name when renaming(like IntelliJ does)?
* Should we add counters to remove duplicate variables? E.g. `type`, `type1`, type2`, etc.


cpud36: I pulled out name suggestion to a submodule in `utils`. As it is now in a separate module I've handled more cases.

Also added a few heuristics to handle common cases. Would be happy to hear feedback on those.
matklad: Going to reabase and refactor myself, I don't want to hold this great addition off :)

Patch:
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs
index 98f3dc6ca0..312ac7ac42 100644
--- a/crates/ide_assists/src/handlers/extract_variable.rs
+++ b/crates/ide_assists/src/handlers/extract_variable.rs
@@ -8,7 +8,7 @@ use syntax::{
 };
 use test_utils::mark;
 
-use crate::{AssistContext, AssistId, AssistKind, Assists};
+use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
 
 // Assist: extract_variable
 //
@@ -54,7 +54,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
 
             let var_name = match &field_shorthand {
                 Some(it) => it.to_string(),
-                None => "var_name".to_string(),
+                None => suggest_name::variable(&to_extract, &ctx.sema),
             };
             let expr_range = match &field_shorthand {
                 Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
@@ -274,8 +274,8 @@ fn foo() {
 "#,
             r#"
 fn foo() {
-    let $0var_name = bar(1 + 1);
-    var_name
+    let $0bar = bar(1 + 1);
+    bar
 }
 "#,
         )
@@ -401,8 +401,8 @@ fn main() {
 ",
             "
 fn main() {
-    let $0var_name = bar.foo();
-    let v = var_name;
+    let $0foo = bar.foo();
+    let v = foo;
 }
 ",
         );
@@ -556,6 +556,202 @@ fn main() {
         )
     }
 
+    #[test]
+    fn extract_var_name_from_type() {
+        check_assist(
+            extract_variable,
+            r#"
+struct Test(i32);
+
+fn foo() -> Test {
+    $0{ Test(10) }$0
+}
+"#,
+            r#"
+struct Test(i32);
+
+fn foo() -> Test {
+    let $0test = { Test(10) };
+    test
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn extract_var_name_from_parameter() {
+        check_assist(
+            extract_variable,
+            r#"
+fn bar(test: u32, size: u32)
+
+fn foo() {
+    bar(1, $01+1$0);
+}
+"#,
+            r#"
+fn bar(test: u32, size: u32)
+
+fn foo() {
+    let $0size = 1+1;
+    bar(1, size);
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn extract_var_parameter_name_has_precedence_over_type() {
+        check_assist(
+            extract_variable,
+            r#"
+struct TextSize(u32);
+fn bar(test: u32, size: TextSize)
+
+fn foo() {
+    bar(1, $0{ TextSize(1+1) }$0);
+}
+"#,
+            r#"
+struct TextSize(u32);
+fn bar(test: u32, size: TextSize)
+
+fn foo() {
+    let $0size = { TextSize(1+1) };
+    bar(1, size);
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn extract_var_name_from_function() {
+        check_assist(
+            extract_variable,
+            r#"
+fn is_required(test: u32, size: u32) -> bool
+
+fn foo() -> bool {
+    $0is_required(1, 2)$0
+}
+"#,
+            r#"
+fn is_required(test: u32, size: u32) -> bool
+
+fn foo() -> bool {
+    let $0is_required = is_required(1, 2);
+    is_required
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn extract_var_name_from_method() {
+        check_assist(
+            extract_variable,
+            r#"
+struct S;
+impl S {
+    fn bar(&self, n: u32) -> u32 { n }
+}
+
+fn foo() -> u32 {
+    $0S.bar(1)$0
+}
+"#,
+            r#"
+struct S;
+impl S {
+    fn bar(&self, n: u32) -> u32 { n }
+}
+
+fn foo() -> u32 {
+    let $0bar = S.bar(1);
+    bar
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn extract_var_name_from_method_param() {
+        check_assist(
+            extract_variable,
+            r#"
+struct S;
+impl S {
+    fn bar(&self, n: u32, size: u32) { n }
+}
+
+fn foo() {
+    S.bar($01 + 1$0, 2)
+}
+"#,
+            r#"
+struct S;
+impl S {
+    fn bar(&self, n: u32, size: u32) { n }
+}
+
+fn foo() {
+    let $0n = 1 + 1;
+    S.bar(n, 2)
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn extract_var_name_from_ufcs_method_param() {
+        check_assist(
+            extract_variable,
+            r#"
+struct S;
+impl S {
+    fn bar(&self, n: u32, size: u32) { n }
+}
+
+fn foo() {
+    S::bar(&S, $01 + 1$0, 2)
+}
+"#,
+            r#"
+struct S;
+impl S {
+    fn bar(&self, n: u32, size: u32) { n }
+}
+
+fn foo() {
+    let $0n = 1 + 1;
+    S::bar(&S, n, 2)
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn extract_var_parameter_name_has_precedence_over_function() {
+        check_assist(
+            extract_variable,
+            r#"
+fn bar(test: u32, size: u32)
+
+fn foo() {
+    bar(1, $0symbol_size(1, 2)$0);
+}
+"#,
+            r#"
+fn bar(test: u32, size: u32)
+
+fn foo() {
+    let $0size = symbol_size(1, 2);
+    bar(1, size);
+}
+"#,
+        )
+    }
+
     #[test]
     fn test_extract_var_for_return_not_applicable() {
         check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index 880ab6fe3e..62f959082e 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -1,5 +1,7 @@
 //! Assorted functions shared by several assists.
 
+pub(crate) mod suggest_name;
+
 use std::ops;
 
 use ast::TypeBoundsOwner;
diff --git a/crates/ide_assists/src/utils/suggest_name.rs b/crates/ide_assists/src/utils/suggest_name.rs
new file mode 100644
index 0000000000..d37c626429
--- /dev/null
+++ b/crates/ide_assists/src/utils/suggest_name.rs
@@ -0,0 +1,877 @@
+//! This module contains functions to suggest names for expressions, functions and other items
+
+use hir::Semantics;
+use ide_db::RootDatabase;
+use itertools::Itertools;
+use stdx::to_lower_snake_case;
+use syntax::{
+    ast::{self, NameOwner},
+    match_ast, AstNode,
+};
+
+/// Trait names, that will be ignored when in `impl Trait` and `dyn Trait`
+const USELESS_TRAITS: &[&str] = &["Send", "Sync", "Copy", "Clone", "Eq", "PartialEq"];
+/// Identifier names that won't be suggested, ever
+///
+/// **NOTE**: they all must be snake lower case
+const USELESS_NAMES: &[&str] =
+    &["new", "default", "option", "some", "none", "ok", "err", "str", "string"];
+/// Generic types replaced by their first argument
+///
+/// # Examples
+/// `Option<Name>` -> `Name`
+/// `Result<User, Error>` -> `User`
+const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
+/// Prefixes to strip from methods names
+///
+/// # Examples
+/// `vec.as_slice()` -> `slice`
+/// `args.into_config()` -> `config`
+/// `bytes.to_vec()` -> `vec`
+const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
+/// Useless methods that are stripped from expression
+///
+/// # Examples
+/// `var.name().to_string()` -> `var.name()`
+const USELESS_METHODS: &[&str] = &[
+    "to_string",
+    "as_str",
+    "to_owned",
+    "as_ref",
+    "clone",
+    "cloned",
+    "expect",
+    "expect_none",
+    "unwrap",
+    "unwrap_none",
+    "unwrap_or",
+    "unwrap_or_default",
+    "unwrap_or_else",
+    "unwrap_unchecked",
+    "iter",
+    "into_iter",
+    "iter_mut",
+];
+
+/// Suggest name of variable for given expression
+///
+/// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name.
+/// I.e. it doesn't look for names in scope.
+///
+/// # Current implementation
+///
+/// In current implementation, the function tries to get the name from
+/// the following sources:
+///
+/// * if expr is an argument to function/method, use paramter name
+/// * if expr is a function/method call, use function name
+/// * expression type name if it exists (E.g. `()`, `fn() -> ()` or `!` do not have names)
+/// * fallback: `var_name`
+///
+/// It also applies heuristics to filter out less informative names
+///
+/// Currently it sticks to the first name found.
+pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String {
+    // `from_param` does not benifit from stripping
+    // it need the largest context possible
+    // so we check firstmost
+    if let Some(name) = from_param(expr, sema) {
+        return name;
+    }
+
+    let mut next_expr = Some(expr.clone());
+    while let Some(expr) = next_expr {
+        let name = from_call(&expr).or_else(|| from_type(&expr, sema));
+        if let Some(name) = name {
+            return name;
+        }
+
+        match expr {
+            ast::Expr::RefExpr(inner) => next_expr = inner.expr(),
+            ast::Expr::BoxExpr(inner) => next_expr = inner.expr(),
+            ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(),
+            // ast::Expr::BlockExpr(block) => expr = block.tail_expr(),
+            ast::Expr::CastExpr(inner) => next_expr = inner.expr(),
+            ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => {
+                next_expr = method.receiver();
+            }
+            ast::Expr::ParenExpr(inner) => next_expr = inner.expr(),
+            ast::Expr::TryExpr(inner) => next_expr = inner.expr(),
+            ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::PrefixOp::Deref) => {
+                next_expr = prefix.expr()
+            }
+            _ => break,
+        }
+    }
+
+    "var_name".to_string()
+}
+
+fn normalize(name: &str) -> Option<String> {
+    let name = to_lower_snake_case(name);
+
+    if USELESS_NAMES.contains(&name.as_str()) {
+        return None;
+    }
+
+    if !is_valid_name(&name) {
+        return None;
+    }
+
+    Some(name)
+}
+
+fn is_valid_name(name: &str) -> bool {
+    match syntax::lex_single_syntax_kind(name) {
+        Some((syntax::SyntaxKind::IDENT, _error)) => true,
+        _ => false,
+    }
+}
+
+fn is_useless_method(method: &ast::MethodCallExpr) -> bool {
+    let ident = method.name_ref().and_then(|it| it.ident_token());
+
+    if let Some(ident) = ident {
+        USELESS_METHODS.contains(&ident.text())
+    } else {
+        false
+    }
+}
+
+fn from_call(expr: &ast::Expr) -> Option<String> {
+    from_func_call(expr).or_else(|| from_method_call(expr))
+}
+
+fn from_func_call(expr: &ast::Expr) -> Option<String> {
+    let call = match expr {
+        ast::Expr::CallExpr(call) => call,
+        _ => return None,
+    };
+    let func = match call.expr()? {
+        ast::Expr::PathExpr(path) => path,
+        _ => return None,
+    };
+    let ident = func.path()?.segment()?.name_ref()?.ident_token()?;
+    normalize(ident.text())
+}
+
+fn from_method_call(expr: &ast::Expr) -> Option<String> {
+    let method = match expr {
+        ast::Expr::MethodCallExpr(call) => call,
+        _ => return None,
+    };
+    let ident = method.name_ref()?.ident_token()?;
+    let mut name = ident.text();
+
+    if USELESS_METHODS.contains(&name) {
+        return None;
+    }
+
+    for prefix in USELESS_METHOD_PREFIXES {
+        if let Some(suffix) = name.strip_prefix(prefix) {
+            name = suffix;
+            break;
+        }
+    }
+
+    normalize(&name)
+}
+
+fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
+    let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?;
+    let args_parent = arg_list.syntax().parent()?;
+    let func = match_ast! {
+        match args_parent {
+            ast::CallExpr(call) => {
+                let func = call.expr()?;
+                let func_ty = sema.type_of_expr(&func)?;
+                func_ty.as_callable(sema.db)?
+            },
+            ast::MethodCallExpr(method) => sema.resolve_method_call_as_callable(&method)?,
+            _ => return None,
+        }
+    };
+
+    let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap();
+    let (pat, _) = func.params(sema.db).into_iter().nth(idx)?;
+    let pat = match pat? {
+        either::Either::Right(pat) => pat,
+        _ => return None,
+    };
+    let name = var_name_from_pat(&pat)?;
+    normalize(&name.to_string())
+}
+
+fn var_name_from_pat(pat: &ast::Pat) -> Option<ast::Name> {
+    match pat {
+        ast::Pat::IdentPat(var) => var.name(),
+        ast::Pat::RefPat(ref_pat) => var_name_from_pat(&ref_pat.pat()?),
+        ast::Pat::BoxPat(box_pat) => var_name_from_pat(&box_pat.pat()?),
+        _ => None,
+    }
+}
+
+fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
+    let ty = sema.type_of_expr(expr)?;
+    let ty = ty.remove_ref().unwrap_or(ty);
+
+    name_of_type(&ty, sema.db)
+}
+
+fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> {
+    let name = if let Some(adt) = ty.as_adt() {
+        let name = adt.name(db).to_string();
+
+        if WRAPPER_TYPES.contains(&name.as_str()) {
+            let inner_ty = ty.type_parameters().next()?;
+            return name_of_type(&inner_ty, db);
+        }
+
+        name
+    } else if let Some(trait_) = ty.as_dyn_trait() {
+        trait_name(&trait_, db)?
+    } else if let Some(traits) = ty.as_impl_traits(db) {
+        let mut iter = traits.into_iter().filter_map(|t| trait_name(&t, db));
+        let name = iter.next()?;
+        if iter.next().is_some() {
+            return None;
+        }
+        name
+    } else {
+        return None;
+    };
+    normalize(&name)
+}
+
+fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option<String> {
+    let name = trait_.name(db).to_string();
+    if USELESS_TRAITS.contains(&name.as_str()) {
+        return None;
+    }
+    Some(name)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::tests::check_name_suggestion;
+
+    mod from_func_call {
+        use super::*;
+
+        #[test]
+        fn no_args() {
+            check_name_suggestion(
+                |e, _| from_func_call(e),
+                r#"
+                fn foo() {
+                    $0bar()$0
+                }"#,
+                "bar",
+            );
+        }
+
+        #[test]
+        fn single_arg() {
+            check_name_suggestion(
+                |e, _| from_func_call(e),
+                r#"
+                fn foo() {
+                    $0bar(1)$0
+                }"#,
+                "bar",
+            );
+        }
+
+        #[test]
+        fn many_args() {
+            check_name_suggestion(
+                |e, _| from_func_call(e),
+                r#"
+                fn foo() {
+                    $0bar(1, 2, 3)$0
+                }"#,
+                "bar",
+            );
+        }
+
+        #[test]
+        fn path() {
+            check_name_suggestion(
+                |e, _| from_func_call(e),
+                r#"
+                fn foo() {
+                    $0i32::bar(1, 2, 3)$0
+                }"#,
+                "bar",
+            );
+        }
+
+        #[test]
+        fn generic_params() {
+            check_name_suggestion(
+                |e, _| from_func_call(e),
+                r#"
+                fn foo() {
+                    $0bar::<i32>(1, 2, 3)$0
+                }"#,
+                "bar",
+            );
+        }
+    }
+
+    mod from_method_call {
+        use super::*;
+
+        #[test]
+        fn no_args() {
+            check_name_suggestion(
+                |e, _| from_method_call(e),
+                r#"
+                fn foo() {
+                    $0bar.frobnicate()$0
+                }"#,
+                "frobnicate",
+            );
+        }
+
+        #[test]
+        fn generic_params() {
+            check_name_suggestion(
+                |e, _| from_method_call(e),
+                r#"
+                fn foo() {
+                    $0bar.frobnicate::<i32, u32>()$0
+                }"#,
+                "frobnicate",
+            );
+        }
+
+        #[test]
+        fn to_name() {
+            check_name_suggestion(
+                |e, _| from_method_call(e),
+                r#"
+                struct Args;
+                struct Config;
+                impl Args {
+                    fn to_config(&self) -> Config {}
+                }
+                fn foo() {
+                    $0Args.to_config()$0;
+                }"#,
+                "config",
+            );
+        }
+    }
+
+    mod from_param {
+        use crate::tests::check_name_suggestion_not_applicable;
+
+        use super::*;
+
+        #[test]
+        fn plain_func() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                fn bar(n: i32, m: u32);
+                fn foo() {
+                    bar($01$0, 2)
+                }"#,
+                "n",
+            );
+        }
+
+        #[test]
+        fn mut_param() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                fn bar(mut n: i32, m: u32);
+                fn foo() {
+                    bar($01$0, 2)
+                }"#,
+                "n",
+            );
+        }
+
+        #[test]
+        fn func_does_not_exist() {
+            check_name_suggestion_not_applicable(
+                from_param,
+                r#"
+                fn foo() {
+                    bar($01$0, 2)
+                }"#,
+            );
+        }
+
+        #[test]
+        fn unnamed_param() {
+            check_name_suggestion_not_applicable(
+                from_param,
+                r#"
+                fn bar(_: i32, m: u32);
+                fn foo() {
+                    bar($01$0, 2)
+                }"#,
+            );
+        }
+
+        #[test]
+        fn tuple_pat() {
+            check_name_suggestion_not_applicable(
+                from_param,
+                r#"
+                fn bar((n, k): (i32, i32), m: u32);
+                fn foo() {
+                    bar($0(1, 2)$0, 3)
+                }"#,
+            );
+        }
+
+        #[test]
+        fn ref_pat() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                fn bar(&n: &i32, m: u32);
+                fn foo() {
+                    bar($0&1$0, 3)
+                }"#,
+                "n",
+            );
+        }
+
+        #[test]
+        fn box_pat() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                fn bar(box n: &i32, m: u32);
+                fn foo() {
+                    bar($01$0, 3)
+                }"#,
+                "n",
+            );
+        }
+
+        #[test]
+        fn param_out_of_index() {
+            check_name_suggestion_not_applicable(
+                from_param,
+                r#"
+                fn bar(n: i32, m: u32);
+                fn foo() {
+                    bar(1, 2, $03$0)
+                }"#,
+            );
+        }
+
+        #[test]
+        fn generic_param_resolved() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                fn bar<T>(n: T, m: u32);
+                fn foo() {
+                    bar($01$0, 2)
+                }"#,
+                "n",
+            );
+        }
+
+        #[test]
+        fn generic_param_unresolved() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                fn bar<T>(n: T, m: u32);
+                fn foo<T>(x: T) {
+                    bar($0x$0, 2)
+                }"#,
+                "n",
+            );
+        }
+
+        #[test]
+        fn method() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                struct S;
+                impl S {
+                    fn bar(&self, n: i32, m: u32);
+                }
+                fn foo() {
+                    S.bar($01$0, 2)
+                }"#,
+                "n",
+            );
+        }
+
+        #[test]
+        fn method_ufcs() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                struct S;
+                impl S {
+                    fn bar(&self, n: i32, m: u32);
+                }
+                fn foo() {
+                    S::bar(&S, $01$0, 2)
+                }"#,
+                "n",
+            );
+        }
+
+        #[test]
+        fn method_self() {
+            check_name_suggestion_not_applicable(
+                from_param,
+                r#"
+                struct S;
+                impl S {
+                    fn bar(&self, n: i32, m: u32);
+                }
+                fn foo() {
+                    S::bar($0&S$0, 1, 2)
+                }"#,
+            );
+        }
+
+        #[test]
+        fn method_self_named() {
+            check_name_suggestion(
+                from_param,
+                r#"
+                struct S;
+                impl S {
+                    fn bar(strukt: &Self, n: i32, m: u32);
+                }
+                fn foo() {
+                    S::bar($0&S$0, 1, 2)
+                }"#,
+                "strukt",
+            );
+        }
+    }
+
+    mod from_type {
+        use crate::tests::check_name_suggestion_not_applicable;
+
+        use super::*;
+
+        #[test]
+        fn i32() {
+            check_name_suggestion_not_applicable(
+                from_type,
+                r#"
+                fn foo() {
+                    let _: i32 = $01$0;
+                }"#,
+            );
+        }
+
+        #[test]
+        fn u64() {
+            check_name_suggestion_not_applicable(
+                from_type,
+                r#"
+                fn foo() {
+                    let _: u64 = $01$0;
+                }"#,
+            );
+        }
+
+        #[test]
+        fn bool() {
+            check_name_suggestion_not_applicable(
+                from_type,
+                r#"
+                fn foo() {
+                    let _: bool = $0true$0;
+                }"#,
+            );
+        }
+
+        #[test]
+        fn struct_unit() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                struct Seed;
+                fn foo() {
+                    let _ = $0Seed$0;
+                }"#,
+                "seed",
+            );
+        }
+
+        #[test]
+        fn struct_unit_to_snake() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                struct SeedState;
+                fn foo() {
+                    let _ = $0SeedState$0;
+                }"#,
+                "seed_state",
+            );
+        }
+
+        #[test]
+        fn struct_single_arg() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                struct Seed(u32);
+                fn foo() {
+                    let _ = $0Seed(0)$0;
+                }"#,
+                "seed",
+            );
+        }
+
+        #[test]
+        fn struct_with_fields() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                struct Seed { value: u32 }
+                fn foo() {
+                    let _ = $0Seed { value: 0 }$0;
+                }"#,
+                "seed",
+            );
+        }
+
+        #[test]
+        fn enum_() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                enum Kind { A, B }
+                fn foo() {
+                    let _ = $0Kind::A$0;
+                }"#,
+                "kind",
+            );
+        }
+
+        #[test]
+        fn enum_generic_resolved() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                enum Kind<T> { A(T), B }
+                fn foo() {
+                    let _ = $0Kind::A(1)$0;
+                }"#,
+                "kind",
+            );
+        }
+
+        #[test]
+        fn enum_generic_unresolved() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                enum Kind<T> { A(T), B }
+                fn foo<T>(x: T) {
+                    let _ = $0Kind::A(x)$0;
+                }"#,
+                "kind",
+            );
+        }
+
+        #[test]
+        fn dyn_trait() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                trait DynHandler {}
+                fn bar() -> dyn DynHandler {}
+                fn foo() {
+                    $0bar()$0;
+                }"#,
+                "dyn_handler",
+            );
+        }
+
+        #[test]
+        fn impl_trait() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                trait StaticHandler {}
+                fn bar() -> impl StaticHandler {}
+                fn foo() {
+                    $0bar()$0;
+                }"#,
+                "static_handler",
+            );
+        }
+
+        #[test]
+        fn impl_trait_plus_clone() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                trait StaticHandler {}
+                trait Clone {}
+                fn bar() -> impl StaticHandler + Clone {}
+                fn foo() {
+                    $0bar()$0;
+                }"#,
+                "static_handler",
+            );
+        }
+
+        #[test]
+        fn impl_trait_plus_lifetime() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                trait StaticHandler {}
+                trait Clone {}
+                fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {}
+                fn foo() {
+                    $0bar(&1)$0;
+                }"#,
+                "static_handler",
+            );
+        }
+
+        #[test]
+        fn impl_trait_plus_trait() {
+            check_name_suggestion_not_applicable(
+                from_type,
+                r#"
+                trait Handler {}
+                trait StaticHandler {}
+                fn bar() -> impl StaticHandler + Handler {}
+                fn foo() {
+                    $0bar()$0;
+                }"#,
+            );
+        }
+
+        #[test]
+        fn ref_value() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                struct Seed;
+                fn bar() -> &Seed {}
+                fn foo() {
+                    $0bar()$0;
+                }"#,
+                "seed",
+            );
+        }
+
+        #[test]
+        fn box_value() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                struct Box<T>(*const T);
+                struct Seed;
+                fn bar() -> Box<Seed> {}
+                fn foo() {
+                    $0bar()$0;
+                }"#,
+                "seed",
+            );
+        }
+
+        #[test]
+        fn box_generic() {
+            check_name_suggestion_not_applicable(
+                from_type,
+                r#"
+                struct Box<T>(*const T);
+                fn bar<T>() -> Box<T> {}
+                fn foo<T>() {
+                    $0bar::<T>()$0;
+                }"#,
+            );
+        }
+
+        #[test]
+        fn option_value() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                enum Option<T> { Some(T) }
+                struct Seed;
+                fn bar() -> Option<Seed> {}
+                fn foo() {
+                    $0bar()$0;
+                }"#,
+                "seed",
+            );
+        }
+
+        #[test]
+        fn result_value() {
+            check_name_suggestion(
+                from_type,
+                r#"
+                enum Result<T, E> { Ok(T), Err(E) }
+                struct Seed;
+                struct Error;
+                fn bar() -> Result<Seed, Error> {}
+                fn foo() {
+                    $0bar()$0;
+                }"#,
+                "seed",
+            );
+        }
+    }
+
+    mod variable {
+        use super::*;
+
+        #[test]
+        fn ref_call() {
+            check_name_suggestion(
+                |e, c| Some(variable(e, c)),
+                r#"
+                fn foo() {
+                    $0&bar(1, 3)$0
+                }"#,
+                "bar",
+            );
+        }
+
+        #[test]
+        fn name_to_string() {
+            check_name_suggestion(
+                |e, c| Some(variable(e, c)),
+                r#"
+                fn foo() {
+                    $0function.name().to_string()$0
+                }"#,
+                "name",
+            );
+        }
+
+        #[test]
+        fn nested_useless_method() {
+            check_name_suggestion(
+                |e, c| Some(variable(e, c)),
+                r#"
+                fn foo() {
+                    $0function.name().as_ref().unwrap().to_string()$0
+                }"#,
+                "name",
+            );
+        }
+    }
+}


Test patch:
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index b7f6167607..66820058b6 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -12,7 +12,7 @@ use ide_db::{
     RootDatabase,
 };
 use stdx::{format_to, trim_indent};
-use syntax::TextRange;
+use syntax::{ast, AstNode, TextRange};
 use test_utils::{assert_eq_text, extract_offset};
 
 use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
@@ -180,6 +180,50 @@ fn labels(assists: &[Assist]) -> String {
     labels.into_iter().collect::<String>()
 }
 
+pub(crate) type NameSuggestion = fn(&ast::Expr, &Semantics<'_, RootDatabase>) -> Option<String>;
+
+#[track_caller]
+pub(crate) fn check_name_suggestion(
+    suggestion: NameSuggestion,
+    ra_fixture: &str,
+    suggested_name: &str,
+) {
+    check_name(suggestion, ra_fixture, Some(suggested_name));
+}
+
+#[track_caller]
+pub(crate) fn check_name_suggestion_not_applicable(suggestion: NameSuggestion, ra_fixture: &str) {
+    check_name(suggestion, ra_fixture, None);
+}
+
+#[track_caller]
+fn check_name(suggestion: NameSuggestion, ra_fixture: &str, expected: Option<&str>) {
+    let (db, file_with_carret_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
+    let frange = FileRange { file_id: file_with_carret_id, range: range_or_offset.into() };
+
+    let sema = Semantics::new(&db);
+    let source_file = sema.parse(frange.file_id);
+    let element = source_file.syntax().covering_element(frange.range);
+    let expr =
+        element.ancestors().find_map(ast::Expr::cast).expect("selection is not an expression");
+    assert_eq!(
+        expr.syntax().text_range(),
+        frange.range,
+        "selection is not an expression(yet contained in one)"
+    );
+
+    let name = suggestion(&expr, &sema);
+
+    match (name, expected) {
+        (Some(name), Some(expected_name)) => {
+            assert_eq_text!(&name, expected_name);
+        }
+        (Some(_), None) => panic!("name suggestion should not be applicable"),
+        (None, Some(_)) => panic!("name suggestion is not applicable"),
+        (None, None) => (),
+    }
+}
+
 #[test]
 fn assist_order_field_struct() {
     let before = "struct Foo { $0bar: u32 }";