aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/utils/suggest_name.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/utils/suggest_name.rs')
-rw-r--r--crates/ide_assists/src/utils/suggest_name.rs729
1 files changed, 729 insertions, 0 deletions
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..533624c1f
--- /dev/null
+++ b/crates/ide_assists/src/utils/suggest_name.rs
@@ -0,0 +1,729 @@
1//! This module contains functions to suggest names for expressions, functions and other items
2
3use hir::Semantics;
4use ide_db::RootDatabase;
5use itertools::Itertools;
6use stdx::to_lower_snake_case;
7use syntax::{
8 ast::{self, NameOwner},
9 match_ast, AstNode,
10};
11
12/// Trait names, that will be ignored when in `impl Trait` and `dyn Trait`
13const USELESS_TRAITS: &[&str] = &["Send", "Sync", "Copy", "Clone", "Eq", "PartialEq"];
14
15/// Identifier names that won't be suggested, ever
16///
17/// **NOTE**: they all must be snake lower case
18const USELESS_NAMES: &[&str] =
19 &["new", "default", "option", "some", "none", "ok", "err", "str", "string"];
20
21/// Generic types replaced by their first argument
22///
23/// # Examples
24/// `Option<Name>` -> `Name`
25/// `Result<User, Error>` -> `User`
26const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
27
28/// Prefixes to strip from methods names
29///
30/// # Examples
31/// `vec.as_slice()` -> `slice`
32/// `args.into_config()` -> `config`
33/// `bytes.to_vec()` -> `vec`
34const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
35
36/// Useless methods that are stripped from expression
37///
38/// # Examples
39/// `var.name().to_string()` -> `var.name()`
40const USELESS_METHODS: &[&str] = &[
41 "to_string",
42 "as_str",
43 "to_owned",
44 "as_ref",
45 "clone",
46 "cloned",
47 "expect",
48 "expect_none",
49 "unwrap",
50 "unwrap_none",
51 "unwrap_or",
52 "unwrap_or_default",
53 "unwrap_or_else",
54 "unwrap_unchecked",
55 "iter",
56 "into_iter",
57 "iter_mut",
58];
59
60/// Suggest name of variable for given expression
61///
62/// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name.
63/// I.e. it doesn't look for names in scope.
64///
65/// # Current implementation
66///
67/// In current implementation, the function tries to get the name from
68/// the following sources:
69///
70/// * if expr is an argument to function/method, use paramter name
71/// * if expr is a function/method call, use function name
72/// * expression type name if it exists (E.g. `()`, `fn() -> ()` or `!` do not have names)
73/// * fallback: `var_name`
74///
75/// It also applies heuristics to filter out less informative names
76///
77/// Currently it sticks to the first name found.
78pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String {
79 // `from_param` does not benifit from stripping
80 // it need the largest context possible
81 // so we check firstmost
82 if let Some(name) = from_param(expr, sema) {
83 return name;
84 }
85
86 let mut next_expr = Some(expr.clone());
87 while let Some(expr) = next_expr {
88 let name = from_call(&expr).or_else(|| from_type(&expr, sema));
89 if let Some(name) = name {
90 return name;
91 }
92
93 match expr {
94 ast::Expr::RefExpr(inner) => next_expr = inner.expr(),
95 ast::Expr::BoxExpr(inner) => next_expr = inner.expr(),
96 ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(),
97 // ast::Expr::BlockExpr(block) => expr = block.tail_expr(),
98 ast::Expr::CastExpr(inner) => next_expr = inner.expr(),
99 ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => {
100 next_expr = method.receiver();
101 }
102 ast::Expr::ParenExpr(inner) => next_expr = inner.expr(),
103 ast::Expr::TryExpr(inner) => next_expr = inner.expr(),
104 ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::PrefixOp::Deref) => {
105 next_expr = prefix.expr()
106 }
107 _ => break,
108 }
109 }
110
111 "var_name".to_string()
112}
113
114fn normalize(name: &str) -> Option<String> {
115 let name = to_lower_snake_case(name);
116
117 if USELESS_NAMES.contains(&name.as_str()) {
118 return None;
119 }
120
121 if !is_valid_name(&name) {
122 return None;
123 }
124
125 Some(name)
126}
127
128fn is_valid_name(name: &str) -> bool {
129 match syntax::lex_single_syntax_kind(name) {
130 Some((syntax::SyntaxKind::IDENT, _error)) => true,
131 _ => false,
132 }
133}
134
135fn is_useless_method(method: &ast::MethodCallExpr) -> bool {
136 let ident = method.name_ref().and_then(|it| it.ident_token());
137
138 if let Some(ident) = ident {
139 USELESS_METHODS.contains(&ident.text())
140 } else {
141 false
142 }
143}
144
145fn from_call(expr: &ast::Expr) -> Option<String> {
146 from_func_call(expr).or_else(|| from_method_call(expr))
147}
148
149fn from_func_call(expr: &ast::Expr) -> Option<String> {
150 let call = match expr {
151 ast::Expr::CallExpr(call) => call,
152 _ => return None,
153 };
154 let func = match call.expr()? {
155 ast::Expr::PathExpr(path) => path,
156 _ => return None,
157 };
158 let ident = func.path()?.segment()?.name_ref()?.ident_token()?;
159 normalize(ident.text())
160}
161
162fn from_method_call(expr: &ast::Expr) -> Option<String> {
163 let method = match expr {
164 ast::Expr::MethodCallExpr(call) => call,
165 _ => return None,
166 };
167 let ident = method.name_ref()?.ident_token()?;
168 let mut name = ident.text();
169
170 if USELESS_METHODS.contains(&name) {
171 return None;
172 }
173
174 for prefix in USELESS_METHOD_PREFIXES {
175 if let Some(suffix) = name.strip_prefix(prefix) {
176 name = suffix;
177 break;
178 }
179 }
180
181 normalize(&name)
182}
183
184fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
185 let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?;
186 let args_parent = arg_list.syntax().parent()?;
187 let func = match_ast! {
188 match args_parent {
189 ast::CallExpr(call) => {
190 let func = call.expr()?;
191 let func_ty = sema.type_of_expr(&func)?;
192 func_ty.as_callable(sema.db)?
193 },
194 ast::MethodCallExpr(method) => sema.resolve_method_call_as_callable(&method)?,
195 _ => return None,
196 }
197 };
198
199 let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap();
200 let (pat, _) = func.params(sema.db).into_iter().nth(idx)?;
201 let pat = match pat? {
202 either::Either::Right(pat) => pat,
203 _ => return None,
204 };
205 let name = var_name_from_pat(&pat)?;
206 normalize(&name.to_string())
207}
208
209fn var_name_from_pat(pat: &ast::Pat) -> Option<ast::Name> {
210 match pat {
211 ast::Pat::IdentPat(var) => var.name(),
212 ast::Pat::RefPat(ref_pat) => var_name_from_pat(&ref_pat.pat()?),
213 ast::Pat::BoxPat(box_pat) => var_name_from_pat(&box_pat.pat()?),
214 _ => None,
215 }
216}
217
218fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
219 let ty = sema.type_of_expr(expr)?;
220 let ty = ty.remove_ref().unwrap_or(ty);
221
222 name_of_type(&ty, sema.db)
223}
224
225fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> {
226 let name = if let Some(adt) = ty.as_adt() {
227 let name = adt.name(db).to_string();
228
229 if WRAPPER_TYPES.contains(&name.as_str()) {
230 let inner_ty = ty.type_parameters().next()?;
231 return name_of_type(&inner_ty, db);
232 }
233
234 name
235 } else if let Some(trait_) = ty.as_dyn_trait() {
236 trait_name(&trait_, db)?
237 } else if let Some(traits) = ty.as_impl_traits(db) {
238 let mut iter = traits.into_iter().filter_map(|t| trait_name(&t, db));
239 let name = iter.next()?;
240 if iter.next().is_some() {
241 return None;
242 }
243 name
244 } else {
245 return None;
246 };
247 normalize(&name)
248}
249
250fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option<String> {
251 let name = trait_.name(db).to_string();
252 if USELESS_TRAITS.contains(&name.as_str()) {
253 return None;
254 }
255 Some(name)
256}
257
258#[cfg(test)]
259mod tests {
260 use ide_db::base_db::{fixture::WithFixture, FileRange};
261
262 use super::*;
263
264 #[track_caller]
265 fn check(ra_fixture: &str, expected: &str) {
266 let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
267 let frange = FileRange { file_id, range: range_or_offset.into() };
268
269 let sema = Semantics::new(&db);
270 let source_file = sema.parse(frange.file_id);
271 let element = source_file.syntax().covering_element(frange.range);
272 let expr =
273 element.ancestors().find_map(ast::Expr::cast).expect("selection is not an expression");
274 assert_eq!(
275 expr.syntax().text_range(),
276 frange.range,
277 "selection is not an expression(yet contained in one)"
278 );
279 let name = variable(&expr, &sema);
280 assert_eq!(&name, expected);
281 }
282
283 #[test]
284 fn no_args() {
285 check(r#"fn foo() { $0bar()$0 }"#, "bar");
286 check(r#"fn foo() { $0bar.frobnicate()$0 }"#, "frobnicate");
287 }
288
289 #[test]
290 fn single_arg() {
291 check(r#"fn foo() { $0bar(1)$0 }"#, "bar");
292 }
293
294 #[test]
295 fn many_args() {
296 check(r#"fn foo() { $0bar(1, 2, 3)$0 }"#, "bar");
297 }
298
299 #[test]
300 fn path() {
301 check(r#"fn foo() { $0i32::bar(1, 2, 3)$0 }"#, "bar");
302 }
303
304 #[test]
305 fn generic_params() {
306 check(r#"fn foo() { $0bar::<i32>(1, 2, 3)$0 }"#, "bar");
307 check(r#"fn foo() { $0bar.frobnicate::<i32, u32>()$0 }"#, "frobnicate");
308 }
309
310 #[test]
311 fn to_name() {
312 check(
313 r#"
314struct Args;
315struct Config;
316impl Args {
317 fn to_config(&self) -> Config {}
318}
319fn foo() {
320 $0Args.to_config()$0;
321}
322"#,
323 "config",
324 );
325 }
326
327 #[test]
328 fn plain_func() {
329 check(
330 r#"
331fn bar(n: i32, m: u32);
332fn foo() { bar($01$0, 2) }
333"#,
334 "n",
335 );
336 }
337
338 #[test]
339 fn mut_param() {
340 check(
341 r#"
342fn bar(mut n: i32, m: u32);
343fn foo() { bar($01$0, 2) }
344"#,
345 "n",
346 );
347 }
348
349 #[test]
350 fn func_does_not_exist() {
351 check(r#"fn foo() { bar($01$0, 2) }"#, "var_name");
352 }
353
354 #[test]
355 fn unnamed_param() {
356 check(
357 r#"
358fn bar(_: i32, m: u32);
359fn foo() { bar($01$0, 2) }
360"#,
361 "var_name",
362 );
363 }
364
365 #[test]
366 fn tuple_pat() {
367 check(
368 r#"
369fn bar((n, k): (i32, i32), m: u32);
370fn foo() {
371 bar($0(1, 2)$0, 3)
372}
373"#,
374 "var_name",
375 );
376 }
377
378 #[test]
379 fn ref_pat() {
380 check(
381 r#"
382fn bar(&n: &i32, m: u32);
383fn foo() { bar($0&1$0, 3) }
384"#,
385 "n",
386 );
387 }
388
389 #[test]
390 fn box_pat() {
391 check(
392 r#"
393fn bar(box n: &i32, m: u32);
394fn foo() { bar($01$0, 3) }
395"#,
396 "n",
397 );
398 }
399
400 #[test]
401 fn param_out_of_index() {
402 check(
403 r#"
404fn bar(n: i32, m: u32);
405fn foo() { bar(1, 2, $03$0) }
406"#,
407 "var_name",
408 );
409 }
410
411 #[test]
412 fn generic_param_resolved() {
413 check(
414 r#"
415fn bar<T>(n: T, m: u32);
416fn foo() { bar($01$0, 2) }
417"#,
418 "n",
419 );
420 }
421
422 #[test]
423 fn generic_param_unresolved() {
424 check(
425 r#"
426fn bar<T>(n: T, m: u32);
427fn foo<T>(x: T) { bar($0x$0, 2) }
428"#,
429 "n",
430 );
431 }
432
433 #[test]
434 fn method() {
435 check(
436 r#"
437struct S;
438impl S { fn bar(&self, n: i32, m: u32); }
439fn foo() { S.bar($01$0, 2) }
440"#,
441 "n",
442 );
443 }
444
445 #[test]
446 fn method_ufcs() {
447 check(
448 r#"
449struct S;
450impl S { fn bar(&self, n: i32, m: u32); }
451fn foo() { S::bar(&S, $01$0, 2) }
452"#,
453 "n",
454 );
455 }
456
457 #[test]
458 fn method_self() {
459 check(
460 r#"
461struct S;
462impl S { fn bar(&self, n: i32, m: u32); }
463fn foo() { S::bar($0&S$0, 1, 2) }
464"#,
465 "s",
466 );
467 }
468
469 #[test]
470 fn method_self_named() {
471 check(
472 r#"
473struct S;
474impl S { fn bar(strukt: &Self, n: i32, m: u32); }
475fn foo() { S::bar($0&S$0, 1, 2) }
476"#,
477 "strukt",
478 );
479 }
480
481 #[test]
482 fn i32() {
483 check(r#"fn foo() { let _: i32 = $01$0; }"#, "var_name");
484 }
485
486 #[test]
487 fn u64() {
488 check(r#"fn foo() { let _: u64 = $01$0; }"#, "var_name");
489 }
490
491 #[test]
492 fn bool() {
493 check(r#"fn foo() { let _: bool = $0true$0; }"#, "var_name");
494 }
495
496 #[test]
497 fn struct_unit() {
498 check(
499 r#"
500struct Seed;
501fn foo() { let _ = $0Seed$0; }
502"#,
503 "seed",
504 );
505 }
506
507 #[test]
508 fn struct_unit_to_snake() {
509 check(
510 r#"
511struct SeedState;
512fn foo() { let _ = $0SeedState$0; }
513"#,
514 "seed_state",
515 );
516 }
517
518 #[test]
519 fn struct_single_arg() {
520 check(
521 r#"
522struct Seed(u32);
523fn foo() { let _ = $0Seed(0)$0; }
524"#,
525 "seed",
526 );
527 }
528
529 #[test]
530 fn struct_with_fields() {
531 check(
532 r#"
533struct Seed { value: u32 }
534fn foo() { let _ = $0Seed { value: 0 }$0; }
535"#,
536 "seed",
537 );
538 }
539
540 #[test]
541 fn enum_() {
542 check(
543 r#"
544enum Kind { A, B }
545fn foo() { let _ = $0Kind::A$0; }
546"#,
547 "kind",
548 );
549 }
550
551 #[test]
552 fn enum_generic_resolved() {
553 check(
554 r#"
555enum Kind<T> { A { x: T }, B }
556fn foo() { let _ = $0Kind::A { x:1 }$0; }
557"#,
558 "kind",
559 );
560 }
561
562 #[test]
563 fn enum_generic_unresolved() {
564 check(
565 r#"
566enum Kind<T> { A { x: T }, B }
567fn foo<T>(x: T) { let _ = $0Kind::A { x }$0; }
568"#,
569 "kind",
570 );
571 }
572
573 #[test]
574 fn dyn_trait() {
575 check(
576 r#"
577trait DynHandler {}
578fn bar() -> dyn DynHandler {}
579fn foo() { $0(bar())$0; }
580"#,
581 "dyn_handler",
582 );
583 }
584
585 #[test]
586 fn impl_trait() {
587 check(
588 r#"
589trait StaticHandler {}
590fn bar() -> impl StaticHandler {}
591fn foo() { $0(bar())$0; }
592"#,
593 "static_handler",
594 );
595 }
596
597 #[test]
598 fn impl_trait_plus_clone() {
599 check(
600 r#"
601trait StaticHandler {}
602trait Clone {}
603fn bar() -> impl StaticHandler + Clone {}
604fn foo() { $0(bar())$0; }
605"#,
606 "static_handler",
607 );
608 }
609
610 #[test]
611 fn impl_trait_plus_lifetime() {
612 check(
613 r#"
614trait StaticHandler {}
615trait Clone {}
616fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {}
617fn foo() { $0(bar(&1))$0; }
618"#,
619 "static_handler",
620 );
621 }
622
623 #[test]
624 fn impl_trait_plus_trait() {
625 check(
626 r#"
627trait Handler {}
628trait StaticHandler {}
629fn bar() -> impl StaticHandler + Handler {}
630fn foo() { $0(bar())$0; }
631"#,
632 "bar",
633 );
634 }
635
636 #[test]
637 fn ref_value() {
638 check(
639 r#"
640struct Seed;
641fn bar() -> &Seed {}
642fn foo() { $0(bar())$0; }
643"#,
644 "seed",
645 );
646 }
647
648 #[test]
649 fn box_value() {
650 check(
651 r#"
652struct Box<T>(*const T);
653struct Seed;
654fn bar() -> Box<Seed> {}
655fn foo() { $0(bar())$0; }
656"#,
657 "seed",
658 );
659 }
660
661 #[test]
662 fn box_generic() {
663 check(
664 r#"
665struct Box<T>(*const T);
666fn bar<T>() -> Box<T> {}
667fn foo<T>() { $0(bar::<T>())$0; }
668"#,
669 "bar",
670 );
671 }
672
673 #[test]
674 fn option_value() {
675 check(
676 r#"
677enum Option<T> { Some(T) }
678struct Seed;
679fn bar() -> Option<Seed> {}
680fn foo() { $0(bar())$0; }
681"#,
682 "seed",
683 );
684 }
685
686 #[test]
687 fn result_value() {
688 check(
689 r#"
690enum Result<T, E> { Ok(T), Err(E) }
691struct Seed;
692struct Error;
693fn bar() -> Result<Seed, Error> {}
694fn foo() { $0(bar())$0; }
695"#,
696 "seed",
697 );
698 }
699
700 #[test]
701 fn ref_call() {
702 check(
703 r#"
704fn foo() { $0&bar(1, 3)$0 }
705"#,
706 "bar",
707 );
708 }
709
710 #[test]
711 fn name_to_string() {
712 check(
713 r#"
714fn foo() { $0function.name().to_string()$0 }
715"#,
716 "name",
717 );
718 }
719
720 #[test]
721 fn nested_useless_method() {
722 check(
723 r#"
724fn foo() { $0function.name().as_ref().unwrap().to_string()$0 }
725"#,
726 "name",
727 );
728 }
729}