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