From afc68277f69572944fd81d61b126732ab29b5d17 Mon Sep 17 00:00:00 2001 From: Vladyslav Katasonov Date: Tue, 16 Feb 2021 23:48:15 +0300 Subject: pull out suggest_name::* to utils; enchance heuristics --- crates/ide_assists/src/utils/suggest_name.rs | 770 +++++++++++++++++++++++++++ 1 file changed, 770 insertions(+) create mode 100644 crates/ide_assists/src/utils/suggest_name.rs (limited to 'crates/ide_assists/src/utils') 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 000000000..345e9af40 --- /dev/null +++ b/crates/ide_assists/src/utils/suggest_name.rs @@ -0,0 +1,770 @@ +//! 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` +/// `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_"]; + +/// 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(expr, sema) + .or_else(|| from_call(expr)) + .or_else(|| from_type(expr, sema)) + .unwrap_or_else(|| "var_name".to_string()) +} + +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 { + match syntax::lex_single_syntax_kind(name) { + Some((syntax::SyntaxKind::IDENT, _error)) => true, + _ => 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 name = normalize(ident.text())?; + + for prefix in USELESS_METHOD_PREFIXES { + if let Some(suffix) = name.strip_prefix(prefix) { + return Some(suffix.to_string()); + } + } + + Some(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)?; + 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 { + 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)?; + let ty = ty.remove_ref().unwrap_or(ty); + + name_of_type(&ty, sema.db) +} + +fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option { + 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 { + 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::(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::()$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(n: T, m: u32); + fn foo() { + bar($01$0, 2) + }"#, + "n", + ); + } + + #[test] + fn generic_param_unresolved() { + check_name_suggestion( + from_param, + r#" + fn bar(n: T, m: u32); + fn foo(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 { A(T), B } + fn foo() { + let _ = $0Kind::A(1)$0; + }"#, + "kind", + ); + } + + #[test] + fn enum_generic_unresolved() { + check_name_suggestion( + from_type, + r#" + enum Kind { A(T), B } + fn foo(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(*const T); + struct Seed; + fn bar() -> Box {} + fn foo() { + $0bar()$0; + }"#, + "seed", + ); + } + + #[test] + fn box_generic() { + check_name_suggestion_not_applicable( + from_type, + r#" + struct Box(*const T); + fn bar() -> Box {} + fn foo() { + $0bar::()$0; + }"#, + ); + } + + #[test] + fn option_value() { + check_name_suggestion( + from_type, + r#" + enum Option { Some(T) } + struct Seed; + fn bar() -> Option {} + fn foo() { + $0bar()$0; + }"#, + "seed", + ); + } + + #[test] + fn result_value() { + check_name_suggestion( + from_type, + r#" + enum Result { Ok(T), Err(E) } + struct Seed; + struct Error; + fn bar() -> Result {} + fn foo() { + $0bar()$0; + }"#, + "seed", + ); + } + } +} -- cgit v1.2.3