From b880890e598062ad671cc11d1dcf61a550293509 Mon Sep 17 00:00:00 2001 From: roife Date: Tue, 3 Sep 2024 05:01:56 +0800 Subject: refactor: move ide_assist::utils::suggest_name to ide-db --- .../ide-assists/src/handlers/extract_variable.rs | 3 +- .../src/handlers/generate_delegate_trait.rs | 3 +- .../src/handlers/introduce_named_generic.rs | 3 +- .../replace_is_method_with_if_let_method.rs | 3 +- .../rust-analyzer/crates/ide-assists/src/utils.rs | 1 - .../crates/ide-assists/src/utils/suggest_name.rs | 821 --------------------- src/tools/rust-analyzer/crates/ide-db/src/lib.rs | 1 + .../ide-db/src/syntax_helpers/suggest_name.rs | 820 ++++++++++++++++++++ 8 files changed, 829 insertions(+), 826 deletions(-) delete mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs create mode 100644 src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs (limited to 'src/tools') diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs index 5ae75bb1ff8..a43a4b5e1a0 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs @@ -1,4 +1,5 @@ use hir::TypeInfo; +use ide_db::syntax_helpers::suggest_name; use syntax::{ ast::{self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, HasName}, ted, NodeOrToken, @@ -6,7 +7,7 @@ use syntax::{ SyntaxNode, T, }; -use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists}; +use crate::{AssistContext, AssistId, AssistKind, Assists}; // Assist: extract_variable // diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs index bf4ce5c907e..c22d19574fb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -2,13 +2,14 @@ use std::ops::Not; use crate::{ assist_context::{AssistContext, Assists}, - utils::{convert_param_list_to_arg_list, suggest_name}, + utils::convert_param_list_to_arg_list, }; use either::Either; use hir::{db::HirDatabase, HasVisibility}; use ide_db::{ assists::{AssistId, GroupLabel}, path_transform::PathTransform, + syntax_helpers::suggest_name, FxHashMap, FxHashSet, }; use itertools::Itertools; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs index 543b7f7ab63..a734a6cc2bc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -1,9 +1,10 @@ +use ide_db::syntax_helpers::suggest_name; use syntax::{ ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams}, ted, }; -use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists}; +use crate::{AssistContext, AssistId, AssistKind, Assists}; // Assist: introduce_named_generic // diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs index 59bb0c45e14..a856da09215 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs @@ -1,9 +1,10 @@ +use ide_db::syntax_helpers::suggest_name; use syntax::{ ast::{self, make, AstNode}, ted, }; -use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists}; +use crate::{AssistContext, AssistId, AssistKind, Assists}; // Assist: replace_is_some_with_if_let_some // diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index b8a6f3b6dbe..19d1ef3157d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -23,7 +23,6 @@ use crate::assist_context::{AssistContext, SourceChangeBuilder}; mod gen_trait_fn_body; pub(crate) mod ref_field_expr; -pub(crate) mod suggest_name; pub(crate) fn unwrap_trivial_block(block_expr: ast::BlockExpr) -> ast::Expr { extract_trivial_expression(&block_expr) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs deleted file mode 100644 index 3130ef06955..00000000000 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs +++ /dev/null @@ -1,821 +0,0 @@ -//! This module contains functions to suggest names for expressions, functions and other items - -use hir::Semantics; -use ide_db::{FxHashSet, RootDatabase}; -use itertools::Itertools; -use stdx::to_lower_snake_case; -use syntax::{ - ast::{self, HasName}, - match_ast, AstNode, Edition, SmolStr, -}; - -/// 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` -/// `Result` -> `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", - "into_future", -]; - -/// Suggest a unique name for generic parameter. -/// -/// `existing_params` is used to check if the name conflicts with existing -/// generic parameters. -/// -/// The function checks if the name conflicts with existing generic parameters. -/// If so, it will try to resolve the conflict by adding a number suffix, e.g. -/// `T`, `T0`, `T1`, ... -pub(crate) fn for_unique_generic_name( - name: &str, - existing_params: &ast::GenericParamList, -) -> SmolStr { - let param_names = existing_params - .generic_params() - .map(|param| match param { - ast::GenericParam::TypeParam(t) => t.name().unwrap().to_string(), - p => p.to_string(), - }) - .collect::>(); - let mut name = name.to_owned(); - let base_len = name.len(); - let mut count = 0; - while param_names.contains(&name) { - name.truncate(base_len); - name.push_str(&count.to_string()); - count += 1; - } - - name.into() -} - -/// Suggest name of impl trait type -/// -/// `existing_params` is used to check if the name conflicts with existing -/// generic parameters. -/// -/// # Current implementation -/// -/// In current implementation, the function tries to get the name from the first -/// character of the name for the first type bound. -/// -/// If the name conflicts with existing generic parameters, it will try to -/// resolve the conflict with `for_unique_generic_name`. -pub(crate) fn for_impl_trait_as_generic( - ty: &ast::ImplTraitType, - existing_params: &ast::GenericParamList, -) -> SmolStr { - let c = ty - .type_bound_list() - .and_then(|bounds| bounds.syntax().text().char_at(0.into())) - .unwrap_or('T'); - - for_unique_generic_name(c.encode_utf8(&mut [0; 4]), existing_params) -} - -/// 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 parameter 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. -// FIXME: Microoptimize and return a `SmolStr` here. -pub(crate) fn for_variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String { - // `from_param` does not benefit 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)).or_else(|| from_field_name(&expr)); - if let Some(name) = name { - return name; - } - - match expr { - ast::Expr::RefExpr(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::UnaryOp::Deref) => { - next_expr = prefix.expr() - } - _ => break, - } - } - - "var_name".to_owned() -} - -fn normalize(name: &str) -> Option { - 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 { - matches!( - ide_db::syntax_helpers::LexedStr::single_token(syntax::Edition::CURRENT_FIXME, name), - Some((syntax::SyntaxKind::IDENT, _error)) - ) -} - -fn is_useless_method(method: &ast::MethodCallExpr) -> bool { - let ident = method.name_ref().and_then(|it| it.ident_token()); - - match ident { - Some(ident) => USELESS_METHODS.contains(&ident.text()), - None => false, - } -} - -fn from_call(expr: &ast::Expr) -> Option { - from_func_call(expr).or_else(|| from_method_call(expr)) -} - -fn from_func_call(expr: &ast::Expr) -> Option { - 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 { - 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 { - 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)?.adjusted(); - 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 param = func.params().into_iter().nth(idx)?; - let pat = sema.source(param)?.value.right()?.pat()?; - let name = var_name_from_pat(&pat)?; - normalize(&name.to_string()) -} - -fn var_name_from_pat(pat: &ast::Pat) -> Option { - 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 { - let ty = sema.type_of_expr(expr)?.adjusted(); - let ty = ty.remove_ref().unwrap_or(ty); - let edition = sema.scope(expr.syntax())?.krate().edition(sema.db); - - name_of_type(&ty, sema.db, edition) -} - -fn name_of_type(ty: &hir::Type, db: &RootDatabase, edition: Edition) -> Option { - let name = if let Some(adt) = ty.as_adt() { - let name = adt.name(db).display(db, edition).to_string(); - - if WRAPPER_TYPES.contains(&name.as_str()) { - let inner_ty = ty.type_arguments().next()?; - return name_of_type(&inner_ty, db, edition); - } - - name - } else if let Some(trait_) = ty.as_dyn_trait() { - trait_name(&trait_, db, edition)? - } else if let Some(traits) = ty.as_impl_traits(db) { - let mut iter = traits.filter_map(|t| trait_name(&t, db, edition)); - 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, edition: Edition) -> Option { - let name = trait_.name(db).display(db, edition).to_string(); - if USELESS_TRAITS.contains(&name.as_str()) { - return None; - } - Some(name) -} - -fn from_field_name(expr: &ast::Expr) -> Option { - let field = match expr { - ast::Expr::FieldExpr(field) => field, - _ => return None, - }; - let ident = field.name_ref()?.ident_token()?; - normalize(ident.text()) -} - -#[cfg(test)] -mod tests { - use hir::FileRange; - use test_fixture::WithFixture; - - use super::*; - - #[track_caller] - fn check(ra_fixture: &str, expected: &str) { - let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); - let frange = FileRange { file_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 = for_variable(&expr, &sema); - assert_eq!(&name, expected); - } - - #[test] - fn no_args() { - check(r#"fn foo() { $0bar()$0 }"#, "bar"); - check(r#"fn foo() { $0bar.frobnicate()$0 }"#, "frobnicate"); - } - - #[test] - fn single_arg() { - check(r#"fn foo() { $0bar(1)$0 }"#, "bar"); - } - - #[test] - fn many_args() { - check(r#"fn foo() { $0bar(1, 2, 3)$0 }"#, "bar"); - } - - #[test] - fn path() { - check(r#"fn foo() { $0i32::bar(1, 2, 3)$0 }"#, "bar"); - } - - #[test] - fn generic_params() { - check(r#"fn foo() { $0bar::(1, 2, 3)$0 }"#, "bar"); - check(r#"fn foo() { $0bar.frobnicate::()$0 }"#, "frobnicate"); - } - - #[test] - fn to_name() { - check( - r#" -struct Args; -struct Config; -impl Args { - fn to_config(&self) -> Config {} -} -fn foo() { - $0Args.to_config()$0; -} -"#, - "config", - ); - } - - #[test] - fn plain_func() { - check( - r#" -fn bar(n: i32, m: u32); -fn foo() { bar($01$0, 2) } -"#, - "n", - ); - } - - #[test] - fn mut_param() { - check( - r#" -fn bar(mut n: i32, m: u32); -fn foo() { bar($01$0, 2) } -"#, - "n", - ); - } - - #[test] - fn func_does_not_exist() { - check(r#"fn foo() { bar($01$0, 2) }"#, "var_name"); - } - - #[test] - fn unnamed_param() { - check( - r#" -fn bar(_: i32, m: u32); -fn foo() { bar($01$0, 2) } -"#, - "var_name", - ); - } - - #[test] - fn tuple_pat() { - check( - r#" -fn bar((n, k): (i32, i32), m: u32); -fn foo() { - bar($0(1, 2)$0, 3) -} -"#, - "var_name", - ); - } - - #[test] - fn ref_pat() { - check( - r#" -fn bar(&n: &i32, m: u32); -fn foo() { bar($0&1$0, 3) } -"#, - "n", - ); - } - - #[test] - fn box_pat() { - check( - r#" -fn bar(box n: &i32, m: u32); -fn foo() { bar($01$0, 3) } -"#, - "n", - ); - } - - #[test] - fn param_out_of_index() { - check( - r#" -fn bar(n: i32, m: u32); -fn foo() { bar(1, 2, $03$0) } -"#, - "var_name", - ); - } - - #[test] - fn generic_param_resolved() { - check( - r#" -fn bar(n: T, m: u32); -fn foo() { bar($01$0, 2) } -"#, - "n", - ); - } - - #[test] - fn generic_param_unresolved() { - check( - r#" -fn bar(n: T, m: u32); -fn foo(x: T) { bar($0x$0, 2) } -"#, - "n", - ); - } - - #[test] - fn method() { - check( - r#" -struct S; -impl S { fn bar(&self, n: i32, m: u32); } -fn foo() { S.bar($01$0, 2) } -"#, - "n", - ); - } - - #[test] - fn method_on_impl_trait() { - check( - r#" -struct S; -trait T { - fn bar(&self, n: i32, m: u32); -} -impl T for S { fn bar(&self, n: i32, m: u32); } -fn foo() { S.bar($01$0, 2) } -"#, - "n", - ); - } - - #[test] - fn method_ufcs() { - check( - 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( - r#" -struct S; -impl S { fn bar(&self, n: i32, m: u32); } -fn foo() { S::bar($0&S$0, 1, 2) } -"#, - "s", - ); - } - - #[test] - fn method_self_named() { - check( - r#" -struct S; -impl S { fn bar(strukt: &Self, n: i32, m: u32); } -fn foo() { S::bar($0&S$0, 1, 2) } -"#, - "strukt", - ); - } - - #[test] - fn i32() { - check(r#"fn foo() { let _: i32 = $01$0; }"#, "var_name"); - } - - #[test] - fn u64() { - check(r#"fn foo() { let _: u64 = $01$0; }"#, "var_name"); - } - - #[test] - fn bool() { - check(r#"fn foo() { let _: bool = $0true$0; }"#, "var_name"); - } - - #[test] - fn struct_unit() { - check( - r#" -struct Seed; -fn foo() { let _ = $0Seed$0; } -"#, - "seed", - ); - } - - #[test] - fn struct_unit_to_snake() { - check( - r#" -struct SeedState; -fn foo() { let _ = $0SeedState$0; } -"#, - "seed_state", - ); - } - - #[test] - fn struct_single_arg() { - check( - r#" -struct Seed(u32); -fn foo() { let _ = $0Seed(0)$0; } -"#, - "seed", - ); - } - - #[test] - fn struct_with_fields() { - check( - r#" -struct Seed { value: u32 } -fn foo() { let _ = $0Seed { value: 0 }$0; } -"#, - "seed", - ); - } - - #[test] - fn enum_() { - check( - r#" -enum Kind { A, B } -fn foo() { let _ = $0Kind::A$0; } -"#, - "kind", - ); - } - - #[test] - fn enum_generic_resolved() { - check( - r#" -enum Kind { A { x: T }, B } -fn foo() { let _ = $0Kind::A { x:1 }$0; } -"#, - "kind", - ); - } - - #[test] - fn enum_generic_unresolved() { - check( - r#" -enum Kind { A { x: T }, B } -fn foo(x: T) { let _ = $0Kind::A { x }$0; } -"#, - "kind", - ); - } - - #[test] - fn dyn_trait() { - check( - r#" -trait DynHandler {} -fn bar() -> dyn DynHandler {} -fn foo() { $0(bar())$0; } -"#, - "dyn_handler", - ); - } - - #[test] - fn impl_trait() { - check( - r#" -trait StaticHandler {} -fn bar() -> impl StaticHandler {} -fn foo() { $0(bar())$0; } -"#, - "static_handler", - ); - } - - #[test] - fn impl_trait_plus_clone() { - check( - r#" -trait StaticHandler {} -trait Clone {} -fn bar() -> impl StaticHandler + Clone {} -fn foo() { $0(bar())$0; } -"#, - "static_handler", - ); - } - - #[test] - fn impl_trait_plus_lifetime() { - check( - r#" -trait StaticHandler {} -trait Clone {} -fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {} -fn foo() { $0(bar(&1))$0; } -"#, - "static_handler", - ); - } - - #[test] - fn impl_trait_plus_trait() { - check( - r#" -trait Handler {} -trait StaticHandler {} -fn bar() -> impl StaticHandler + Handler {} -fn foo() { $0(bar())$0; } -"#, - "bar", - ); - } - - #[test] - fn ref_value() { - check( - r#" -struct Seed; -fn bar() -> &Seed {} -fn foo() { $0(bar())$0; } -"#, - "seed", - ); - } - - #[test] - fn box_value() { - check( - r#" -struct Box(*const T); -struct Seed; -fn bar() -> Box {} -fn foo() { $0(bar())$0; } -"#, - "seed", - ); - } - - #[test] - fn box_generic() { - check( - r#" -struct Box(*const T); -fn bar() -> Box {} -fn foo() { $0(bar::())$0; } -"#, - "bar", - ); - } - - #[test] - fn option_value() { - check( - r#" -enum Option { Some(T) } -struct Seed; -fn bar() -> Option {} -fn foo() { $0(bar())$0; } -"#, - "seed", - ); - } - - #[test] - fn result_value() { - check( - r#" -enum Result { Ok(T), Err(E) } -struct Seed; -struct Error; -fn bar() -> Result {} -fn foo() { $0(bar())$0; } -"#, - "seed", - ); - } - - #[test] - fn ref_call() { - check( - r#" -fn foo() { $0&bar(1, 3)$0 } -"#, - "bar", - ); - } - - #[test] - fn name_to_string() { - check( - r#" -fn foo() { $0function.name().to_string()$0 } -"#, - "name", - ); - } - - #[test] - fn nested_useless_method() { - check( - r#" -fn foo() { $0function.name().as_ref().unwrap().to_string()$0 } -"#, - "name", - ); - } - - #[test] - fn struct_field_name() { - check( - r#" -struct S { - some_field: T; -} -fn foo(some_struct: S) { $0some_struct.some_field$0 } -"#, - "some_field", - ); - } -} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs index 8a2068e9039..ab161f0ce57 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs @@ -38,6 +38,7 @@ pub mod syntax_helpers { pub mod format_string_exprs; pub use hir::insert_whitespace_into_node; pub mod node_ext; + pub mod suggest_name; pub use parser::LexedStr; } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs new file mode 100644 index 00000000000..14128e74438 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs @@ -0,0 +1,820 @@ +//! This module contains functions to suggest names for expressions, functions and other items + +use hir::Semantics; +use itertools::Itertools; +use rustc_hash::FxHashSet; +use stdx::to_lower_snake_case; +use syntax::{ + ast::{self, HasName}, + match_ast, AstNode, Edition, SmolStr, +}; + +use crate::RootDatabase; + +/// 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` +/// `Result` -> `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", + "into_future", +]; + +/// Suggest a unique name for generic parameter. +/// +/// `existing_params` is used to check if the name conflicts with existing +/// generic parameters. +/// +/// The function checks if the name conflicts with existing generic parameters. +/// If so, it will try to resolve the conflict by adding a number suffix, e.g. +/// `T`, `T0`, `T1`, ... +pub fn for_unique_generic_name(name: &str, existing_params: &ast::GenericParamList) -> SmolStr { + let param_names = existing_params + .generic_params() + .map(|param| match param { + ast::GenericParam::TypeParam(t) => t.name().unwrap().to_string(), + p => p.to_string(), + }) + .collect::>(); + let mut name = name.to_owned(); + let base_len = name.len(); + let mut count = 0; + while param_names.contains(&name) { + name.truncate(base_len); + name.push_str(&count.to_string()); + count += 1; + } + + name.into() +} + +/// Suggest name of impl trait type +/// +/// `existing_params` is used to check if the name conflicts with existing +/// generic parameters. +/// +/// # Current implementation +/// +/// In current implementation, the function tries to get the name from the first +/// character of the name for the first type bound. +/// +/// If the name conflicts with existing generic parameters, it will try to +/// resolve the conflict with `for_unique_generic_name`. +pub fn for_impl_trait_as_generic( + ty: &ast::ImplTraitType, + existing_params: &ast::GenericParamList, +) -> SmolStr { + let c = ty + .type_bound_list() + .and_then(|bounds| bounds.syntax().text().char_at(0.into())) + .unwrap_or('T'); + + for_unique_generic_name(c.encode_utf8(&mut [0; 4]), existing_params) +} + +/// 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 parameter 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. +// FIXME: Microoptimize and return a `SmolStr` here. +pub fn for_variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String { + // `from_param` does not benefit 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)).or_else(|| from_field_name(&expr)); + if let Some(name) = name { + return name; + } + + match expr { + ast::Expr::RefExpr(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::UnaryOp::Deref) => { + next_expr = prefix.expr() + } + _ => break, + } + } + + "var_name".to_owned() +} + +fn normalize(name: &str) -> Option { + 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 { + matches!( + super::LexedStr::single_token(syntax::Edition::CURRENT_FIXME, name), + Some((syntax::SyntaxKind::IDENT, _error)) + ) +} + +fn is_useless_method(method: &ast::MethodCallExpr) -> bool { + let ident = method.name_ref().and_then(|it| it.ident_token()); + + match ident { + Some(ident) => USELESS_METHODS.contains(&ident.text()), + None => false, + } +} + +fn from_call(expr: &ast::Expr) -> Option { + from_func_call(expr).or_else(|| from_method_call(expr)) +} + +fn from_func_call(expr: &ast::Expr) -> Option { + 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 { + 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 { + 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)?.adjusted(); + 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 param = func.params().into_iter().nth(idx)?; + let pat = sema.source(param)?.value.right()?.pat()?; + let name = var_name_from_pat(&pat)?; + normalize(&name.to_string()) +} + +fn var_name_from_pat(pat: &ast::Pat) -> Option { + 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 { + let ty = sema.type_of_expr(expr)?.adjusted(); + let ty = ty.remove_ref().unwrap_or(ty); + let edition = sema.scope(expr.syntax())?.krate().edition(sema.db); + + name_of_type(&ty, sema.db, edition) +} + +fn name_of_type(ty: &hir::Type, db: &RootDatabase, edition: Edition) -> Option { + let name = if let Some(adt) = ty.as_adt() { + let name = adt.name(db).display(db, edition).to_string(); + + if WRAPPER_TYPES.contains(&name.as_str()) { + let inner_ty = ty.type_arguments().next()?; + return name_of_type(&inner_ty, db, edition); + } + + name + } else if let Some(trait_) = ty.as_dyn_trait() { + trait_name(&trait_, db, edition)? + } else if let Some(traits) = ty.as_impl_traits(db) { + let mut iter = traits.filter_map(|t| trait_name(&t, db, edition)); + 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, edition: Edition) -> Option { + let name = trait_.name(db).display(db, edition).to_string(); + if USELESS_TRAITS.contains(&name.as_str()) { + return None; + } + Some(name) +} + +fn from_field_name(expr: &ast::Expr) -> Option { + let field = match expr { + ast::Expr::FieldExpr(field) => field, + _ => return None, + }; + let ident = field.name_ref()?.ident_token()?; + normalize(ident.text()) +} + +#[cfg(test)] +mod tests { + use hir::FileRange; + use test_fixture::WithFixture; + + use super::*; + + #[track_caller] + fn check(ra_fixture: &str, expected: &str) { + let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); + let frange = FileRange { file_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 = for_variable(&expr, &sema); + assert_eq!(&name, expected); + } + + #[test] + fn no_args() { + check(r#"fn foo() { $0bar()$0 }"#, "bar"); + check(r#"fn foo() { $0bar.frobnicate()$0 }"#, "frobnicate"); + } + + #[test] + fn single_arg() { + check(r#"fn foo() { $0bar(1)$0 }"#, "bar"); + } + + #[test] + fn many_args() { + check(r#"fn foo() { $0bar(1, 2, 3)$0 }"#, "bar"); + } + + #[test] + fn path() { + check(r#"fn foo() { $0i32::bar(1, 2, 3)$0 }"#, "bar"); + } + + #[test] + fn generic_params() { + check(r#"fn foo() { $0bar::(1, 2, 3)$0 }"#, "bar"); + check(r#"fn foo() { $0bar.frobnicate::()$0 }"#, "frobnicate"); + } + + #[test] + fn to_name() { + check( + r#" +struct Args; +struct Config; +impl Args { + fn to_config(&self) -> Config {} +} +fn foo() { + $0Args.to_config()$0; +} +"#, + "config", + ); + } + + #[test] + fn plain_func() { + check( + r#" +fn bar(n: i32, m: u32); +fn foo() { bar($01$0, 2) } +"#, + "n", + ); + } + + #[test] + fn mut_param() { + check( + r#" +fn bar(mut n: i32, m: u32); +fn foo() { bar($01$0, 2) } +"#, + "n", + ); + } + + #[test] + fn func_does_not_exist() { + check(r#"fn foo() { bar($01$0, 2) }"#, "var_name"); + } + + #[test] + fn unnamed_param() { + check( + r#" +fn bar(_: i32, m: u32); +fn foo() { bar($01$0, 2) } +"#, + "var_name", + ); + } + + #[test] + fn tuple_pat() { + check( + r#" +fn bar((n, k): (i32, i32), m: u32); +fn foo() { + bar($0(1, 2)$0, 3) +} +"#, + "var_name", + ); + } + + #[test] + fn ref_pat() { + check( + r#" +fn bar(&n: &i32, m: u32); +fn foo() { bar($0&1$0, 3) } +"#, + "n", + ); + } + + #[test] + fn box_pat() { + check( + r#" +fn bar(box n: &i32, m: u32); +fn foo() { bar($01$0, 3) } +"#, + "n", + ); + } + + #[test] + fn param_out_of_index() { + check( + r#" +fn bar(n: i32, m: u32); +fn foo() { bar(1, 2, $03$0) } +"#, + "var_name", + ); + } + + #[test] + fn generic_param_resolved() { + check( + r#" +fn bar(n: T, m: u32); +fn foo() { bar($01$0, 2) } +"#, + "n", + ); + } + + #[test] + fn generic_param_unresolved() { + check( + r#" +fn bar(n: T, m: u32); +fn foo(x: T) { bar($0x$0, 2) } +"#, + "n", + ); + } + + #[test] + fn method() { + check( + r#" +struct S; +impl S { fn bar(&self, n: i32, m: u32); } +fn foo() { S.bar($01$0, 2) } +"#, + "n", + ); + } + + #[test] + fn method_on_impl_trait() { + check( + r#" +struct S; +trait T { + fn bar(&self, n: i32, m: u32); +} +impl T for S { fn bar(&self, n: i32, m: u32); } +fn foo() { S.bar($01$0, 2) } +"#, + "n", + ); + } + + #[test] + fn method_ufcs() { + check( + 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( + r#" +struct S; +impl S { fn bar(&self, n: i32, m: u32); } +fn foo() { S::bar($0&S$0, 1, 2) } +"#, + "s", + ); + } + + #[test] + fn method_self_named() { + check( + r#" +struct S; +impl S { fn bar(strukt: &Self, n: i32, m: u32); } +fn foo() { S::bar($0&S$0, 1, 2) } +"#, + "strukt", + ); + } + + #[test] + fn i32() { + check(r#"fn foo() { let _: i32 = $01$0; }"#, "var_name"); + } + + #[test] + fn u64() { + check(r#"fn foo() { let _: u64 = $01$0; }"#, "var_name"); + } + + #[test] + fn bool() { + check(r#"fn foo() { let _: bool = $0true$0; }"#, "var_name"); + } + + #[test] + fn struct_unit() { + check( + r#" +struct Seed; +fn foo() { let _ = $0Seed$0; } +"#, + "seed", + ); + } + + #[test] + fn struct_unit_to_snake() { + check( + r#" +struct SeedState; +fn foo() { let _ = $0SeedState$0; } +"#, + "seed_state", + ); + } + + #[test] + fn struct_single_arg() { + check( + r#" +struct Seed(u32); +fn foo() { let _ = $0Seed(0)$0; } +"#, + "seed", + ); + } + + #[test] + fn struct_with_fields() { + check( + r#" +struct Seed { value: u32 } +fn foo() { let _ = $0Seed { value: 0 }$0; } +"#, + "seed", + ); + } + + #[test] + fn enum_() { + check( + r#" +enum Kind { A, B } +fn foo() { let _ = $0Kind::A$0; } +"#, + "kind", + ); + } + + #[test] + fn enum_generic_resolved() { + check( + r#" +enum Kind { A { x: T }, B } +fn foo() { let _ = $0Kind::A { x:1 }$0; } +"#, + "kind", + ); + } + + #[test] + fn enum_generic_unresolved() { + check( + r#" +enum Kind { A { x: T }, B } +fn foo(x: T) { let _ = $0Kind::A { x }$0; } +"#, + "kind", + ); + } + + #[test] + fn dyn_trait() { + check( + r#" +trait DynHandler {} +fn bar() -> dyn DynHandler {} +fn foo() { $0(bar())$0; } +"#, + "dyn_handler", + ); + } + + #[test] + fn impl_trait() { + check( + r#" +trait StaticHandler {} +fn bar() -> impl StaticHandler {} +fn foo() { $0(bar())$0; } +"#, + "static_handler", + ); + } + + #[test] + fn impl_trait_plus_clone() { + check( + r#" +trait StaticHandler {} +trait Clone {} +fn bar() -> impl StaticHandler + Clone {} +fn foo() { $0(bar())$0; } +"#, + "static_handler", + ); + } + + #[test] + fn impl_trait_plus_lifetime() { + check( + r#" +trait StaticHandler {} +trait Clone {} +fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {} +fn foo() { $0(bar(&1))$0; } +"#, + "static_handler", + ); + } + + #[test] + fn impl_trait_plus_trait() { + check( + r#" +trait Handler {} +trait StaticHandler {} +fn bar() -> impl StaticHandler + Handler {} +fn foo() { $0(bar())$0; } +"#, + "bar", + ); + } + + #[test] + fn ref_value() { + check( + r#" +struct Seed; +fn bar() -> &Seed {} +fn foo() { $0(bar())$0; } +"#, + "seed", + ); + } + + #[test] + fn box_value() { + check( + r#" +struct Box(*const T); +struct Seed; +fn bar() -> Box {} +fn foo() { $0(bar())$0; } +"#, + "seed", + ); + } + + #[test] + fn box_generic() { + check( + r#" +struct Box(*const T); +fn bar() -> Box {} +fn foo() { $0(bar::())$0; } +"#, + "bar", + ); + } + + #[test] + fn option_value() { + check( + r#" +enum Option { Some(T) } +struct Seed; +fn bar() -> Option {} +fn foo() { $0(bar())$0; } +"#, + "seed", + ); + } + + #[test] + fn result_value() { + check( + r#" +enum Result { Ok(T), Err(E) } +struct Seed; +struct Error; +fn bar() -> Result {} +fn foo() { $0(bar())$0; } +"#, + "seed", + ); + } + + #[test] + fn ref_call() { + check( + r#" +fn foo() { $0&bar(1, 3)$0 } +"#, + "bar", + ); + } + + #[test] + fn name_to_string() { + check( + r#" +fn foo() { $0function.name().to_string()$0 } +"#, + "name", + ); + } + + #[test] + fn nested_useless_method() { + check( + r#" +fn foo() { $0function.name().as_ref().unwrap().to_string()$0 } +"#, + "name", + ); + } + + #[test] + fn struct_field_name() { + check( + r#" +struct S { + some_field: T; +} +fn foo(some_struct: S) { $0some_struct.some_field$0 } +"#, + "some_field", + ); + } +} -- cgit 1.4.1-3-g733a5 From 5f7fcbe0d4dbd34c01dc6ae16e19a0f00035ba2f Mon Sep 17 00:00:00 2001 From: roife Date: Tue, 3 Sep 2024 05:15:21 +0800 Subject: feat: suggest name in let_stmt and fn_param --- .../crates/ide-completion/src/completions.rs | 10 ++++++++++ .../crates/ide-completion/src/completions/pattern.rs | 13 +++++++++++++ .../rust-analyzer/crates/ide-completion/src/context.rs | 1 + .../crates/ide-completion/src/context/analysis.rs | 13 +++++++++++++ .../crates/ide-db/src/syntax_helpers/suggest_name.rs | 18 ++++++++++++++++-- 5 files changed, 53 insertions(+), 2 deletions(-) (limited to 'src/tools') diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs index b537150608b..414627fbaba 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs @@ -617,6 +617,16 @@ impl Completions { } self.add_opt(render_struct_pat(RenderContext::new(ctx), pattern_ctx, strukt, local_name)); } + + pub(crate) fn suggest_name(&mut self, ctx: &CompletionContext<'_>, name: &str) { + let item = CompletionItem::new( + CompletionItemKind::Binding, + ctx.source_range(), + SmolStr::from(name), + ctx.edition, + ); + item.add_to(self, ctx.db); + } } /// Calls the callback for each variant of the provided enum with the path to the variant. diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs index 60cfb7e5a8c..2a06fc40175 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs @@ -1,6 +1,7 @@ //! Completes constants and paths in unqualified patterns. use hir::{db::DefDatabase, AssocItem, ScopeDef}; +use ide_db::syntax_helpers::suggest_name; use syntax::ast::Pat; use crate::{ @@ -45,6 +46,18 @@ pub(crate) fn complete_pattern( return; } + // Suggest name only in let-stmt and fn param + if pattern_ctx.should_suggest_name { + if let Some(suggested) = ctx + .expected_type + .as_ref() + .map(|ty| ty.strip_references()) + .and_then(|ty| suggest_name::for_type(&ty, ctx.db, ctx.edition)) + { + acc.suggest_name(ctx, &suggested); + } + } + let refutable = pattern_ctx.refutability == PatternRefutability::Refutable; let single_variant_enum = |enum_: hir::Enum| ctx.db.enum_data(enum_.into()).variants.len() == 1; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index bcd9df94194..d457ba32bf0 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -264,6 +264,7 @@ pub(crate) struct PatternContext { pub(crate) refutability: PatternRefutability, pub(crate) param_ctx: Option, pub(crate) has_type_ascription: bool, + pub(crate) should_suggest_name: bool, pub(crate) parent_pat: Option, pub(crate) ref_token: Option, pub(crate) mut_token: Option, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index 292c419498d..1f9e3edf625 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -1430,10 +1430,23 @@ fn pattern_context_for( _ => (None, None), }; + // Only suggest name in let-stmt or fn param + let should_suggest_name = matches!( + &pat, + ast::Pat::IdentPat(it) + if it.syntax() + .parent() + .map_or(false, |node| { + let kind = node.kind(); + ast::LetStmt::can_cast(kind) || ast::Param::can_cast(kind) + }) + ); + PatternContext { refutability, param_ctx, has_type_ascription, + should_suggest_name, parent_pat: pat.syntax().parent().and_then(ast::Pat::cast), mut_token, ref_token, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs index 14128e74438..6ee526a67ea 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/suggest_name.rs @@ -60,6 +60,21 @@ const USELESS_METHODS: &[&str] = &[ "into_future", ]; +/// Suggest a name for given type. +/// +/// The function will strip references first, and suggest name from the inner type. +/// +/// - If `ty` is an ADT, it will suggest the name of the ADT. +/// + If `ty` is wrapped in `Box`, `Option` or `Result`, it will suggest the name from the inner type. +/// - If `ty` is a trait, it will suggest the name of the trait. +/// - If `ty` is an `impl Trait`, it will suggest the name of the first trait. +/// +/// If the suggested name conflicts with reserved keywords, it will return `None`. +pub fn for_type(ty: &hir::Type, db: &RootDatabase, edition: Edition) -> Option { + let ty = ty.strip_references(); + name_of_type(&ty, db, edition) +} + /// Suggest a unique name for generic parameter. /// /// `existing_params` is used to check if the name conflicts with existing @@ -269,10 +284,9 @@ fn var_name_from_pat(pat: &ast::Pat) -> Option { fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option { let ty = sema.type_of_expr(expr)?.adjusted(); - let ty = ty.remove_ref().unwrap_or(ty); let edition = sema.scope(expr.syntax())?.krate().edition(sema.db); - name_of_type(&ty, sema.db, edition) + for_type(&ty, sema.db, edition) } fn name_of_type(ty: &hir::Type, db: &RootDatabase, edition: Edition) -> Option { -- cgit 1.4.1-3-g733a5 From ef491f22999bb964ac92b64935d7ffe722e9c5a8 Mon Sep 17 00:00:00 2001 From: roife Date: Tue, 3 Sep 2024 05:16:02 +0800 Subject: tests: suggesting names in completions for let_stmt and fn_param --- .../crates/ide-completion/src/tests/pattern.rs | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) (limited to 'src/tools') diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs index 6a0b67e291a..bd3e7c72bcd 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs @@ -198,6 +198,7 @@ fn foo(a$0: Tuple) { st Unit bn Record {…} Record { field$1 }$0 bn Tuple(…) Tuple($1)$0 + bn tuple kw mut kw ref "#]], @@ -850,3 +851,75 @@ fn foo() { "#, ); } + +#[test] +fn suggest_name_for_pattern() { + check_edit( + "s1", + r#" +struct S1; + +fn foo() { + let $0 = S1; +} +"#, + r#" +struct S1; + +fn foo() { + let s1 = S1; +} +"#, + ); + + check_edit( + "s1", + r#" +struct S1; + +fn foo(s$0: S1) { +} +"#, + r#" +struct S1; + +fn foo(s1: S1) { +} +"#, + ); + + // Tests for &adt + check_edit( + "s1", + r#" +struct S1; + +fn foo() { + let $0 = &S1; +} +"#, + r#" +struct S1; + +fn foo() { + let s1 = &S1; +} +"#, + ); + + // Do not suggest reserved keywords + check_empty( + r#" +struct Struct; + +fn foo() { + let $0 = Struct; +} +"#, + expect![[r#" + st Struct + kw mut + kw ref + "#]], + ); +} -- cgit 1.4.1-3-g733a5