1 //! This module contains functions to suggest names for expressions, functions and other items
4 use ide_db::RootDatabase;
5 use itertools::Itertools;
6 use stdx::to_lower_snake_case;
9 match_ast, AstNode, SmolStr,
12 /// Trait names, that will be ignored when in `impl Trait` and `dyn Trait`
13 const USELESS_TRAITS: &[&str] = &["Send", "Sync", "Copy", "Clone", "Eq", "PartialEq"];
15 /// Identifier names that won't be suggested, ever
17 /// **NOTE**: they all must be snake lower case
18 const USELESS_NAMES: &[&str] =
19 &["new", "default", "option", "some", "none", "ok", "err", "str", "string"];
21 /// Generic types replaced by their first argument
24 /// `Option<Name>` -> `Name`
25 /// `Result<User, Error>` -> `User`
26 const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
28 /// Prefixes to strip from methods names
31 /// `vec.as_slice()` -> `slice`
32 /// `args.into_config()` -> `config`
33 /// `bytes.to_vec()` -> `vec`
34 const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
36 /// Useless methods that are stripped from expression
39 /// `var.name().to_string()` -> `var.name()`
40 const USELESS_METHODS: &[&str] = &[
60 pub(crate) fn for_generic_parameter(ty: &ast::ImplTraitType) -> SmolStr {
63 .and_then(|bounds| bounds.syntax().text().char_at(0.into()))
65 c.encode_utf8(&mut [0; 4]).into()
68 /// Suggest name of variable for given expression
70 /// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name.
71 /// I.e. it doesn't look for names in scope.
73 /// # Current implementation
75 /// In current implementation, the function tries to get the name from
76 /// the following sources:
78 /// * if expr is an argument to function/method, use paramter name
79 /// * if expr is a function/method call, use function name
80 /// * expression type name if it exists (E.g. `()`, `fn() -> ()` or `!` do not have names)
81 /// * fallback: `var_name`
83 /// It also applies heuristics to filter out less informative names
85 /// Currently it sticks to the first name found.
86 // FIXME: Microoptimize and return a `SmolStr` here.
87 pub(crate) fn for_variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String {
88 // `from_param` does not benifit from stripping
89 // it need the largest context possible
90 // so we check firstmost
91 if let Some(name) = from_param(expr, sema) {
95 let mut next_expr = Some(expr.clone());
96 while let Some(expr) = next_expr {
98 from_call(&expr).or_else(|| from_type(&expr, sema)).or_else(|| from_field_name(&expr));
99 if let Some(name) = name {
104 ast::Expr::RefExpr(inner) => next_expr = inner.expr(),
105 ast::Expr::BoxExpr(inner) => next_expr = inner.expr(),
106 ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(),
107 // ast::Expr::BlockExpr(block) => expr = block.tail_expr(),
108 ast::Expr::CastExpr(inner) => next_expr = inner.expr(),
109 ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => {
110 next_expr = method.receiver();
112 ast::Expr::ParenExpr(inner) => next_expr = inner.expr(),
113 ast::Expr::TryExpr(inner) => next_expr = inner.expr(),
114 ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::UnaryOp::Deref) => {
115 next_expr = prefix.expr()
121 "var_name".to_string()
124 fn normalize(name: &str) -> Option<String> {
125 let name = to_lower_snake_case(name);
127 if USELESS_NAMES.contains(&name.as_str()) {
131 if !is_valid_name(&name) {
138 fn is_valid_name(name: &str) -> bool {
139 match ide_db::syntax_helpers::LexedStr::single_token(name) {
140 Some((syntax::SyntaxKind::IDENT, _error)) => true,
145 fn is_useless_method(method: &ast::MethodCallExpr) -> bool {
146 let ident = method.name_ref().and_then(|it| it.ident_token());
149 Some(ident) => USELESS_METHODS.contains(&ident.text()),
154 fn from_call(expr: &ast::Expr) -> Option<String> {
155 from_func_call(expr).or_else(|| from_method_call(expr))
158 fn from_func_call(expr: &ast::Expr) -> Option<String> {
159 let call = match expr {
160 ast::Expr::CallExpr(call) => call,
163 let func = match call.expr()? {
164 ast::Expr::PathExpr(path) => path,
167 let ident = func.path()?.segment()?.name_ref()?.ident_token()?;
168 normalize(ident.text())
171 fn from_method_call(expr: &ast::Expr) -> Option<String> {
172 let method = match expr {
173 ast::Expr::MethodCallExpr(call) => call,
176 let ident = method.name_ref()?.ident_token()?;
177 let mut name = ident.text();
179 if USELESS_METHODS.contains(&name) {
183 for prefix in USELESS_METHOD_PREFIXES {
184 if let Some(suffix) = name.strip_prefix(prefix) {
193 fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
194 let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?;
195 let args_parent = arg_list.syntax().parent()?;
196 let func = match_ast! {
198 ast::CallExpr(call) => {
199 let func = call.expr()?;
200 let func_ty = sema.type_of_expr(&func)?.adjusted();
201 func_ty.as_callable(sema.db)?
203 ast::MethodCallExpr(method) => sema.resolve_method_call_as_callable(&method)?,
208 let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap();
209 let (pat, _) = func.params(sema.db).into_iter().nth(idx)?;
210 let pat = match pat? {
211 either::Either::Right(pat) => pat,
214 let name = var_name_from_pat(&pat)?;
215 normalize(&name.to_string())
218 fn var_name_from_pat(pat: &ast::Pat) -> Option<ast::Name> {
220 ast::Pat::IdentPat(var) => var.name(),
221 ast::Pat::RefPat(ref_pat) => var_name_from_pat(&ref_pat.pat()?),
222 ast::Pat::BoxPat(box_pat) => var_name_from_pat(&box_pat.pat()?),
227 fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
228 let ty = sema.type_of_expr(expr)?.adjusted();
229 let ty = ty.remove_ref().unwrap_or(ty);
231 name_of_type(&ty, sema.db)
234 fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> {
235 let name = if let Some(adt) = ty.as_adt() {
236 let name = adt.name(db).to_string();
238 if WRAPPER_TYPES.contains(&name.as_str()) {
239 let inner_ty = ty.type_arguments().next()?;
240 return name_of_type(&inner_ty, db);
244 } else if let Some(trait_) = ty.as_dyn_trait() {
245 trait_name(&trait_, db)?
246 } else if let Some(traits) = ty.as_impl_traits(db) {
247 let mut iter = traits.filter_map(|t| trait_name(&t, db));
248 let name = iter.next()?;
249 if iter.next().is_some() {
259 fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option<String> {
260 let name = trait_.name(db).to_string();
261 if USELESS_TRAITS.contains(&name.as_str()) {
267 fn from_field_name(expr: &ast::Expr) -> Option<String> {
268 let field = match expr {
269 ast::Expr::FieldExpr(field) => field,
272 let ident = field.name_ref()?.ident_token()?;
273 normalize(ident.text())
278 use ide_db::base_db::{fixture::WithFixture, FileRange};
283 fn check(ra_fixture: &str, expected: &str) {
284 let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
285 let frange = FileRange { file_id, range: range_or_offset.into() };
287 let sema = Semantics::new(&db);
288 let source_file = sema.parse(frange.file_id);
289 let element = source_file.syntax().covering_element(frange.range);
291 element.ancestors().find_map(ast::Expr::cast).expect("selection is not an expression");
293 expr.syntax().text_range(),
295 "selection is not an expression(yet contained in one)"
297 let name = for_variable(&expr, &sema);
298 assert_eq!(&name, expected);
303 check(r#"fn foo() { $0bar()$0 }"#, "bar");
304 check(r#"fn foo() { $0bar.frobnicate()$0 }"#, "frobnicate");
309 check(r#"fn foo() { $0bar(1)$0 }"#, "bar");
314 check(r#"fn foo() { $0bar(1, 2, 3)$0 }"#, "bar");
319 check(r#"fn foo() { $0i32::bar(1, 2, 3)$0 }"#, "bar");
323 fn generic_params() {
324 check(r#"fn foo() { $0bar::<i32>(1, 2, 3)$0 }"#, "bar");
325 check(r#"fn foo() { $0bar.frobnicate::<i32, u32>()$0 }"#, "frobnicate");
335 fn to_config(&self) -> Config {}
338 $0Args.to_config()$0;
349 fn bar(n: i32, m: u32);
350 fn foo() { bar($01$0, 2) }
360 fn bar(mut n: i32, m: u32);
361 fn foo() { bar($01$0, 2) }
368 fn func_does_not_exist() {
369 check(r#"fn foo() { bar($01$0, 2) }"#, "var_name");
376 fn bar(_: i32, m: u32);
377 fn foo() { bar($01$0, 2) }
387 fn bar((n, k): (i32, i32), m: u32);
400 fn bar(&n: &i32, m: u32);
401 fn foo() { bar($0&1$0, 3) }
411 fn bar(box n: &i32, m: u32);
412 fn foo() { bar($01$0, 3) }
419 fn param_out_of_index() {
422 fn bar(n: i32, m: u32);
423 fn foo() { bar(1, 2, $03$0) }
430 fn generic_param_resolved() {
433 fn bar<T>(n: T, m: u32);
434 fn foo() { bar($01$0, 2) }
441 fn generic_param_unresolved() {
444 fn bar<T>(n: T, m: u32);
445 fn foo<T>(x: T) { bar($0x$0, 2) }
456 impl S { fn bar(&self, n: i32, m: u32); }
457 fn foo() { S.bar($01$0, 2) }
464 fn method_on_impl_trait() {
469 fn bar(&self, n: i32, m: u32);
471 impl T for S { fn bar(&self, n: i32, m: u32); }
472 fn foo() { S.bar($01$0, 2) }
483 impl S { fn bar(&self, n: i32, m: u32); }
484 fn foo() { S::bar(&S, $01$0, 2) }
495 impl S { fn bar(&self, n: i32, m: u32); }
496 fn foo() { S::bar($0&S$0, 1, 2) }
503 fn method_self_named() {
507 impl S { fn bar(strukt: &Self, n: i32, m: u32); }
508 fn foo() { S::bar($0&S$0, 1, 2) }
516 check(r#"fn foo() { let _: i32 = $01$0; }"#, "var_name");
521 check(r#"fn foo() { let _: u64 = $01$0; }"#, "var_name");
526 check(r#"fn foo() { let _: bool = $0true$0; }"#, "var_name");
534 fn foo() { let _ = $0Seed$0; }
541 fn struct_unit_to_snake() {
545 fn foo() { let _ = $0SeedState$0; }
552 fn struct_single_arg() {
556 fn foo() { let _ = $0Seed(0)$0; }
563 fn struct_with_fields() {
566 struct Seed { value: u32 }
567 fn foo() { let _ = $0Seed { value: 0 }$0; }
578 fn foo() { let _ = $0Kind::A$0; }
585 fn enum_generic_resolved() {
588 enum Kind<T> { A { x: T }, B }
589 fn foo() { let _ = $0Kind::A { x:1 }$0; }
596 fn enum_generic_unresolved() {
599 enum Kind<T> { A { x: T }, B }
600 fn foo<T>(x: T) { let _ = $0Kind::A { x }$0; }
611 fn bar() -> dyn DynHandler {}
612 fn foo() { $0(bar())$0; }
622 trait StaticHandler {}
623 fn bar() -> impl StaticHandler {}
624 fn foo() { $0(bar())$0; }
631 fn impl_trait_plus_clone() {
634 trait StaticHandler {}
636 fn bar() -> impl StaticHandler + Clone {}
637 fn foo() { $0(bar())$0; }
644 fn impl_trait_plus_lifetime() {
647 trait StaticHandler {}
649 fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {}
650 fn foo() { $0(bar(&1))$0; }
657 fn impl_trait_plus_trait() {
661 trait StaticHandler {}
662 fn bar() -> impl StaticHandler + Handler {}
663 fn foo() { $0(bar())$0; }
675 fn foo() { $0(bar())$0; }
685 struct Box<T>(*const T);
687 fn bar() -> Box<Seed> {}
688 fn foo() { $0(bar())$0; }
698 struct Box<T>(*const T);
699 fn bar<T>() -> Box<T> {}
700 fn foo<T>() { $0(bar::<T>())$0; }
710 enum Option<T> { Some(T) }
712 fn bar() -> Option<Seed> {}
713 fn foo() { $0(bar())$0; }
723 enum Result<T, E> { Ok(T), Err(E) }
726 fn bar() -> Result<Seed, Error> {}
727 fn foo() { $0(bar())$0; }
737 fn foo() { $0&bar(1, 3)$0 }
744 fn name_to_string() {
747 fn foo() { $0function.name().to_string()$0 }
754 fn nested_useless_method() {
757 fn foo() { $0function.name().as_ref().unwrap().to_string()$0 }
764 fn struct_field_name() {
770 fn foo<T>(some_struct: S<T>) { $0some_struct.some_field$0 }