diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/handlers/extract_variable.rs | 208 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 46 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/utils/suggest_name.rs | 877 |
4 files changed, 1126 insertions, 7 deletions
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs index 98f3dc6ca..312ac7ac4 100644 --- a/crates/ide_assists/src/handlers/extract_variable.rs +++ b/crates/ide_assists/src/handlers/extract_variable.rs | |||
@@ -8,7 +8,7 @@ use syntax::{ | |||
8 | }; | 8 | }; |
9 | use test_utils::mark; | 9 | use test_utils::mark; |
10 | 10 | ||
11 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 11 | use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists}; |
12 | 12 | ||
13 | // Assist: extract_variable | 13 | // Assist: extract_variable |
14 | // | 14 | // |
@@ -54,7 +54,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
54 | 54 | ||
55 | let var_name = match &field_shorthand { | 55 | let var_name = match &field_shorthand { |
56 | Some(it) => it.to_string(), | 56 | Some(it) => it.to_string(), |
57 | None => "var_name".to_string(), | 57 | None => suggest_name::variable(&to_extract, &ctx.sema), |
58 | }; | 58 | }; |
59 | let expr_range = match &field_shorthand { | 59 | let expr_range = match &field_shorthand { |
60 | Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), | 60 | Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), |
@@ -274,8 +274,8 @@ fn foo() { | |||
274 | "#, | 274 | "#, |
275 | r#" | 275 | r#" |
276 | fn foo() { | 276 | fn foo() { |
277 | let $0var_name = bar(1 + 1); | 277 | let $0bar = bar(1 + 1); |
278 | var_name | 278 | bar |
279 | } | 279 | } |
280 | "#, | 280 | "#, |
281 | ) | 281 | ) |
@@ -401,8 +401,8 @@ fn main() { | |||
401 | ", | 401 | ", |
402 | " | 402 | " |
403 | fn main() { | 403 | fn main() { |
404 | let $0var_name = bar.foo(); | 404 | let $0foo = bar.foo(); |
405 | let v = var_name; | 405 | let v = foo; |
406 | } | 406 | } |
407 | ", | 407 | ", |
408 | ); | 408 | ); |
@@ -557,6 +557,202 @@ fn main() { | |||
557 | } | 557 | } |
558 | 558 | ||
559 | #[test] | 559 | #[test] |
560 | fn extract_var_name_from_type() { | ||
561 | check_assist( | ||
562 | extract_variable, | ||
563 | r#" | ||
564 | struct Test(i32); | ||
565 | |||
566 | fn foo() -> Test { | ||
567 | $0{ Test(10) }$0 | ||
568 | } | ||
569 | "#, | ||
570 | r#" | ||
571 | struct Test(i32); | ||
572 | |||
573 | fn foo() -> Test { | ||
574 | let $0test = { Test(10) }; | ||
575 | test | ||
576 | } | ||
577 | "#, | ||
578 | ) | ||
579 | } | ||
580 | |||
581 | #[test] | ||
582 | fn extract_var_name_from_parameter() { | ||
583 | check_assist( | ||
584 | extract_variable, | ||
585 | r#" | ||
586 | fn bar(test: u32, size: u32) | ||
587 | |||
588 | fn foo() { | ||
589 | bar(1, $01+1$0); | ||
590 | } | ||
591 | "#, | ||
592 | r#" | ||
593 | fn bar(test: u32, size: u32) | ||
594 | |||
595 | fn foo() { | ||
596 | let $0size = 1+1; | ||
597 | bar(1, size); | ||
598 | } | ||
599 | "#, | ||
600 | ) | ||
601 | } | ||
602 | |||
603 | #[test] | ||
604 | fn extract_var_parameter_name_has_precedence_over_type() { | ||
605 | check_assist( | ||
606 | extract_variable, | ||
607 | r#" | ||
608 | struct TextSize(u32); | ||
609 | fn bar(test: u32, size: TextSize) | ||
610 | |||
611 | fn foo() { | ||
612 | bar(1, $0{ TextSize(1+1) }$0); | ||
613 | } | ||
614 | "#, | ||
615 | r#" | ||
616 | struct TextSize(u32); | ||
617 | fn bar(test: u32, size: TextSize) | ||
618 | |||
619 | fn foo() { | ||
620 | let $0size = { TextSize(1+1) }; | ||
621 | bar(1, size); | ||
622 | } | ||
623 | "#, | ||
624 | ) | ||
625 | } | ||
626 | |||
627 | #[test] | ||
628 | fn extract_var_name_from_function() { | ||
629 | check_assist( | ||
630 | extract_variable, | ||
631 | r#" | ||
632 | fn is_required(test: u32, size: u32) -> bool | ||
633 | |||
634 | fn foo() -> bool { | ||
635 | $0is_required(1, 2)$0 | ||
636 | } | ||
637 | "#, | ||
638 | r#" | ||
639 | fn is_required(test: u32, size: u32) -> bool | ||
640 | |||
641 | fn foo() -> bool { | ||
642 | let $0is_required = is_required(1, 2); | ||
643 | is_required | ||
644 | } | ||
645 | "#, | ||
646 | ) | ||
647 | } | ||
648 | |||
649 | #[test] | ||
650 | fn extract_var_name_from_method() { | ||
651 | check_assist( | ||
652 | extract_variable, | ||
653 | r#" | ||
654 | struct S; | ||
655 | impl S { | ||
656 | fn bar(&self, n: u32) -> u32 { n } | ||
657 | } | ||
658 | |||
659 | fn foo() -> u32 { | ||
660 | $0S.bar(1)$0 | ||
661 | } | ||
662 | "#, | ||
663 | r#" | ||
664 | struct S; | ||
665 | impl S { | ||
666 | fn bar(&self, n: u32) -> u32 { n } | ||
667 | } | ||
668 | |||
669 | fn foo() -> u32 { | ||
670 | let $0bar = S.bar(1); | ||
671 | bar | ||
672 | } | ||
673 | "#, | ||
674 | ) | ||
675 | } | ||
676 | |||
677 | #[test] | ||
678 | fn extract_var_name_from_method_param() { | ||
679 | check_assist( | ||
680 | extract_variable, | ||
681 | r#" | ||
682 | struct S; | ||
683 | impl S { | ||
684 | fn bar(&self, n: u32, size: u32) { n } | ||
685 | } | ||
686 | |||
687 | fn foo() { | ||
688 | S.bar($01 + 1$0, 2) | ||
689 | } | ||
690 | "#, | ||
691 | r#" | ||
692 | struct S; | ||
693 | impl S { | ||
694 | fn bar(&self, n: u32, size: u32) { n } | ||
695 | } | ||
696 | |||
697 | fn foo() { | ||
698 | let $0n = 1 + 1; | ||
699 | S.bar(n, 2) | ||
700 | } | ||
701 | "#, | ||
702 | ) | ||
703 | } | ||
704 | |||
705 | #[test] | ||
706 | fn extract_var_name_from_ufcs_method_param() { | ||
707 | check_assist( | ||
708 | extract_variable, | ||
709 | r#" | ||
710 | struct S; | ||
711 | impl S { | ||
712 | fn bar(&self, n: u32, size: u32) { n } | ||
713 | } | ||
714 | |||
715 | fn foo() { | ||
716 | S::bar(&S, $01 + 1$0, 2) | ||
717 | } | ||
718 | "#, | ||
719 | r#" | ||
720 | struct S; | ||
721 | impl S { | ||
722 | fn bar(&self, n: u32, size: u32) { n } | ||
723 | } | ||
724 | |||
725 | fn foo() { | ||
726 | let $0n = 1 + 1; | ||
727 | S::bar(&S, n, 2) | ||
728 | } | ||
729 | "#, | ||
730 | ) | ||
731 | } | ||
732 | |||
733 | #[test] | ||
734 | fn extract_var_parameter_name_has_precedence_over_function() { | ||
735 | check_assist( | ||
736 | extract_variable, | ||
737 | r#" | ||
738 | fn bar(test: u32, size: u32) | ||
739 | |||
740 | fn foo() { | ||
741 | bar(1, $0symbol_size(1, 2)$0); | ||
742 | } | ||
743 | "#, | ||
744 | r#" | ||
745 | fn bar(test: u32, size: u32) | ||
746 | |||
747 | fn foo() { | ||
748 | let $0size = symbol_size(1, 2); | ||
749 | bar(1, size); | ||
750 | } | ||
751 | "#, | ||
752 | ) | ||
753 | } | ||
754 | |||
755 | #[test] | ||
560 | fn test_extract_var_for_return_not_applicable() { | 756 | fn test_extract_var_for_return_not_applicable() { |
561 | check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); | 757 | check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); |
562 | } | 758 | } |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index b7f616760..66820058b 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -12,7 +12,7 @@ use ide_db::{ | |||
12 | RootDatabase, | 12 | RootDatabase, |
13 | }; | 13 | }; |
14 | use stdx::{format_to, trim_indent}; | 14 | use stdx::{format_to, trim_indent}; |
15 | use syntax::TextRange; | 15 | use syntax::{ast, AstNode, TextRange}; |
16 | use test_utils::{assert_eq_text, extract_offset}; | 16 | use test_utils::{assert_eq_text, extract_offset}; |
17 | 17 | ||
18 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; | 18 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; |
@@ -180,6 +180,50 @@ fn labels(assists: &[Assist]) -> String { | |||
180 | labels.into_iter().collect::<String>() | 180 | labels.into_iter().collect::<String>() |
181 | } | 181 | } |
182 | 182 | ||
183 | pub(crate) type NameSuggestion = fn(&ast::Expr, &Semantics<'_, RootDatabase>) -> Option<String>; | ||
184 | |||
185 | #[track_caller] | ||
186 | pub(crate) fn check_name_suggestion( | ||
187 | suggestion: NameSuggestion, | ||
188 | ra_fixture: &str, | ||
189 | suggested_name: &str, | ||
190 | ) { | ||
191 | check_name(suggestion, ra_fixture, Some(suggested_name)); | ||
192 | } | ||
193 | |||
194 | #[track_caller] | ||
195 | pub(crate) fn check_name_suggestion_not_applicable(suggestion: NameSuggestion, ra_fixture: &str) { | ||
196 | check_name(suggestion, ra_fixture, None); | ||
197 | } | ||
198 | |||
199 | #[track_caller] | ||
200 | fn check_name(suggestion: NameSuggestion, ra_fixture: &str, expected: Option<&str>) { | ||
201 | let (db, file_with_carret_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); | ||
202 | let frange = FileRange { file_id: file_with_carret_id, range: range_or_offset.into() }; | ||
203 | |||
204 | let sema = Semantics::new(&db); | ||
205 | let source_file = sema.parse(frange.file_id); | ||
206 | let element = source_file.syntax().covering_element(frange.range); | ||
207 | let expr = | ||
208 | element.ancestors().find_map(ast::Expr::cast).expect("selection is not an expression"); | ||
209 | assert_eq!( | ||
210 | expr.syntax().text_range(), | ||
211 | frange.range, | ||
212 | "selection is not an expression(yet contained in one)" | ||
213 | ); | ||
214 | |||
215 | let name = suggestion(&expr, &sema); | ||
216 | |||
217 | match (name, expected) { | ||
218 | (Some(name), Some(expected_name)) => { | ||
219 | assert_eq_text!(&name, expected_name); | ||
220 | } | ||
221 | (Some(_), None) => panic!("name suggestion should not be applicable"), | ||
222 | (None, Some(_)) => panic!("name suggestion is not applicable"), | ||
223 | (None, None) => (), | ||
224 | } | ||
225 | } | ||
226 | |||
183 | #[test] | 227 | #[test] |
184 | fn assist_order_field_struct() { | 228 | fn assist_order_field_struct() { |
185 | let before = "struct Foo { $0bar: u32 }"; | 229 | let before = "struct Foo { $0bar: u32 }"; |
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 880ab6fe3..62f959082 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! Assorted functions shared by several assists. | 1 | //! Assorted functions shared by several assists. |
2 | 2 | ||
3 | pub(crate) mod suggest_name; | ||
4 | |||
3 | use std::ops; | 5 | use std::ops; |
4 | 6 | ||
5 | use ast::TypeBoundsOwner; | 7 | use ast::TypeBoundsOwner; |
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..d37c62642 --- /dev/null +++ b/crates/ide_assists/src/utils/suggest_name.rs | |||
@@ -0,0 +1,877 @@ | |||
1 | //! This module contains functions to suggest names for expressions, functions and other items | ||
2 | |||
3 | use hir::Semantics; | ||
4 | use ide_db::RootDatabase; | ||
5 | use itertools::Itertools; | ||
6 | use stdx::to_lower_snake_case; | ||
7 | use 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` | ||
13 | const 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 | ||
17 | const 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` | ||
24 | const 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` | ||
31 | const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"]; | ||
32 | /// Useless methods that are stripped from expression | ||
33 | /// | ||
34 | /// # Examples | ||
35 | /// `var.name().to_string()` -> `var.name()` | ||
36 | const USELESS_METHODS: &[&str] = &[ | ||
37 | "to_string", | ||
38 | "as_str", | ||
39 | "to_owned", | ||
40 | "as_ref", | ||
41 | "clone", | ||
42 | "cloned", | ||
43 | "expect", | ||
44 | "expect_none", | ||
45 | "unwrap", | ||
46 | "unwrap_none", | ||
47 | "unwrap_or", | ||
48 | "unwrap_or_default", | ||
49 | "unwrap_or_else", | ||
50 | "unwrap_unchecked", | ||
51 | "iter", | ||
52 | "into_iter", | ||
53 | "iter_mut", | ||
54 | ]; | ||
55 | |||
56 | /// Suggest name of variable for given expression | ||
57 | /// | ||
58 | /// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name. | ||
59 | /// I.e. it doesn't look for names in scope. | ||
60 | /// | ||
61 | /// # Current implementation | ||
62 | /// | ||
63 | /// In current implementation, the function tries to get the name from | ||
64 | /// the following sources: | ||
65 | /// | ||
66 | /// * if expr is an argument to function/method, use paramter name | ||
67 | /// * if expr is a function/method call, use function name | ||
68 | /// * expression type name if it exists (E.g. `()`, `fn() -> ()` or `!` do not have names) | ||
69 | /// * fallback: `var_name` | ||
70 | /// | ||
71 | /// It also applies heuristics to filter out less informative names | ||
72 | /// | ||
73 | /// Currently it sticks to the first name found. | ||
74 | pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String { | ||
75 | // `from_param` does not benifit from stripping | ||
76 | // it need the largest context possible | ||
77 | // so we check firstmost | ||
78 | if let Some(name) = from_param(expr, sema) { | ||
79 | return name; | ||
80 | } | ||
81 | |||
82 | let mut next_expr = Some(expr.clone()); | ||
83 | while let Some(expr) = next_expr { | ||
84 | let name = from_call(&expr).or_else(|| from_type(&expr, sema)); | ||
85 | if let Some(name) = name { | ||
86 | return name; | ||
87 | } | ||
88 | |||
89 | match expr { | ||
90 | ast::Expr::RefExpr(inner) => next_expr = inner.expr(), | ||
91 | ast::Expr::BoxExpr(inner) => next_expr = inner.expr(), | ||
92 | ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(), | ||
93 | // ast::Expr::BlockExpr(block) => expr = block.tail_expr(), | ||
94 | ast::Expr::CastExpr(inner) => next_expr = inner.expr(), | ||
95 | ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => { | ||
96 | next_expr = method.receiver(); | ||
97 | } | ||
98 | ast::Expr::ParenExpr(inner) => next_expr = inner.expr(), | ||
99 | ast::Expr::TryExpr(inner) => next_expr = inner.expr(), | ||
100 | ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::PrefixOp::Deref) => { | ||
101 | next_expr = prefix.expr() | ||
102 | } | ||
103 | _ => break, | ||
104 | } | ||
105 | } | ||
106 | |||
107 | "var_name".to_string() | ||
108 | } | ||
109 | |||
110 | fn normalize(name: &str) -> Option<String> { | ||
111 | let name = to_lower_snake_case(name); | ||
112 | |||
113 | if USELESS_NAMES.contains(&name.as_str()) { | ||
114 | return None; | ||
115 | } | ||
116 | |||
117 | if !is_valid_name(&name) { | ||
118 | return None; | ||
119 | } | ||
120 | |||
121 | Some(name) | ||
122 | } | ||
123 | |||
124 | fn is_valid_name(name: &str) -> bool { | ||
125 | match syntax::lex_single_syntax_kind(name) { | ||
126 | Some((syntax::SyntaxKind::IDENT, _error)) => true, | ||
127 | _ => false, | ||
128 | } | ||
129 | } | ||
130 | |||
131 | fn is_useless_method(method: &ast::MethodCallExpr) -> bool { | ||
132 | let ident = method.name_ref().and_then(|it| it.ident_token()); | ||
133 | |||
134 | if let Some(ident) = ident { | ||
135 | USELESS_METHODS.contains(&ident.text()) | ||
136 | } else { | ||
137 | false | ||
138 | } | ||
139 | } | ||
140 | |||
141 | fn from_call(expr: &ast::Expr) -> Option<String> { | ||
142 | from_func_call(expr).or_else(|| from_method_call(expr)) | ||
143 | } | ||
144 | |||
145 | fn from_func_call(expr: &ast::Expr) -> Option<String> { | ||
146 | let call = match expr { | ||
147 | ast::Expr::CallExpr(call) => call, | ||
148 | _ => return None, | ||
149 | }; | ||
150 | let func = match call.expr()? { | ||
151 | ast::Expr::PathExpr(path) => path, | ||
152 | _ => return None, | ||
153 | }; | ||
154 | let ident = func.path()?.segment()?.name_ref()?.ident_token()?; | ||
155 | normalize(ident.text()) | ||
156 | } | ||
157 | |||
158 | fn from_method_call(expr: &ast::Expr) -> Option<String> { | ||
159 | let method = match expr { | ||
160 | ast::Expr::MethodCallExpr(call) => call, | ||
161 | _ => return None, | ||
162 | }; | ||
163 | let ident = method.name_ref()?.ident_token()?; | ||
164 | let mut name = ident.text(); | ||
165 | |||
166 | if USELESS_METHODS.contains(&name) { | ||
167 | return None; | ||
168 | } | ||
169 | |||
170 | for prefix in USELESS_METHOD_PREFIXES { | ||
171 | if let Some(suffix) = name.strip_prefix(prefix) { | ||
172 | name = suffix; | ||
173 | break; | ||
174 | } | ||
175 | } | ||
176 | |||
177 | normalize(&name) | ||
178 | } | ||
179 | |||
180 | fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> { | ||
181 | let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?; | ||
182 | let args_parent = arg_list.syntax().parent()?; | ||
183 | let func = match_ast! { | ||
184 | match args_parent { | ||
185 | ast::CallExpr(call) => { | ||
186 | let func = call.expr()?; | ||
187 | let func_ty = sema.type_of_expr(&func)?; | ||
188 | func_ty.as_callable(sema.db)? | ||
189 | }, | ||
190 | ast::MethodCallExpr(method) => sema.resolve_method_call_as_callable(&method)?, | ||
191 | _ => return None, | ||
192 | } | ||
193 | }; | ||
194 | |||
195 | let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap(); | ||
196 | let (pat, _) = func.params(sema.db).into_iter().nth(idx)?; | ||
197 | let pat = match pat? { | ||
198 | either::Either::Right(pat) => pat, | ||
199 | _ => return None, | ||
200 | }; | ||
201 | let name = var_name_from_pat(&pat)?; | ||
202 | normalize(&name.to_string()) | ||
203 | } | ||
204 | |||
205 | fn var_name_from_pat(pat: &ast::Pat) -> Option<ast::Name> { | ||
206 | match pat { | ||
207 | ast::Pat::IdentPat(var) => var.name(), | ||
208 | ast::Pat::RefPat(ref_pat) => var_name_from_pat(&ref_pat.pat()?), | ||
209 | ast::Pat::BoxPat(box_pat) => var_name_from_pat(&box_pat.pat()?), | ||
210 | _ => None, | ||
211 | } | ||
212 | } | ||
213 | |||
214 | fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> { | ||
215 | let ty = sema.type_of_expr(expr)?; | ||
216 | let ty = ty.remove_ref().unwrap_or(ty); | ||
217 | |||
218 | name_of_type(&ty, sema.db) | ||
219 | } | ||
220 | |||
221 | fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> { | ||
222 | let name = if let Some(adt) = ty.as_adt() { | ||
223 | let name = adt.name(db).to_string(); | ||
224 | |||
225 | if WRAPPER_TYPES.contains(&name.as_str()) { | ||
226 | let inner_ty = ty.type_parameters().next()?; | ||
227 | return name_of_type(&inner_ty, db); | ||
228 | } | ||
229 | |||
230 | name | ||
231 | } else if let Some(trait_) = ty.as_dyn_trait() { | ||
232 | trait_name(&trait_, db)? | ||
233 | } else if let Some(traits) = ty.as_impl_traits(db) { | ||
234 | let mut iter = traits.into_iter().filter_map(|t| trait_name(&t, db)); | ||
235 | let name = iter.next()?; | ||
236 | if iter.next().is_some() { | ||
237 | return None; | ||
238 | } | ||
239 | name | ||
240 | } else { | ||
241 | return None; | ||
242 | }; | ||
243 | normalize(&name) | ||
244 | } | ||
245 | |||
246 | fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option<String> { | ||
247 | let name = trait_.name(db).to_string(); | ||
248 | if USELESS_TRAITS.contains(&name.as_str()) { | ||
249 | return None; | ||
250 | } | ||
251 | Some(name) | ||
252 | } | ||
253 | |||
254 | #[cfg(test)] | ||
255 | mod tests { | ||
256 | use super::*; | ||
257 | |||
258 | use crate::tests::check_name_suggestion; | ||
259 | |||
260 | mod from_func_call { | ||
261 | use super::*; | ||
262 | |||
263 | #[test] | ||
264 | fn no_args() { | ||
265 | check_name_suggestion( | ||
266 | |e, _| from_func_call(e), | ||
267 | r#" | ||
268 | fn foo() { | ||
269 | $0bar()$0 | ||
270 | }"#, | ||
271 | "bar", | ||
272 | ); | ||
273 | } | ||
274 | |||
275 | #[test] | ||
276 | fn single_arg() { | ||
277 | check_name_suggestion( | ||
278 | |e, _| from_func_call(e), | ||
279 | r#" | ||
280 | fn foo() { | ||
281 | $0bar(1)$0 | ||
282 | }"#, | ||
283 | "bar", | ||
284 | ); | ||
285 | } | ||
286 | |||
287 | #[test] | ||
288 | fn many_args() { | ||
289 | check_name_suggestion( | ||
290 | |e, _| from_func_call(e), | ||
291 | r#" | ||
292 | fn foo() { | ||
293 | $0bar(1, 2, 3)$0 | ||
294 | }"#, | ||
295 | "bar", | ||
296 | ); | ||
297 | } | ||
298 | |||
299 | #[test] | ||
300 | fn path() { | ||
301 | check_name_suggestion( | ||
302 | |e, _| from_func_call(e), | ||
303 | r#" | ||
304 | fn foo() { | ||
305 | $0i32::bar(1, 2, 3)$0 | ||
306 | }"#, | ||
307 | "bar", | ||
308 | ); | ||
309 | } | ||
310 | |||
311 | #[test] | ||
312 | fn generic_params() { | ||
313 | check_name_suggestion( | ||
314 | |e, _| from_func_call(e), | ||
315 | r#" | ||
316 | fn foo() { | ||
317 | $0bar::<i32>(1, 2, 3)$0 | ||
318 | }"#, | ||
319 | "bar", | ||
320 | ); | ||
321 | } | ||
322 | } | ||
323 | |||
324 | mod from_method_call { | ||
325 | use super::*; | ||
326 | |||
327 | #[test] | ||
328 | fn no_args() { | ||
329 | check_name_suggestion( | ||
330 | |e, _| from_method_call(e), | ||
331 | r#" | ||
332 | fn foo() { | ||
333 | $0bar.frobnicate()$0 | ||
334 | }"#, | ||
335 | "frobnicate", | ||
336 | ); | ||
337 | } | ||
338 | |||
339 | #[test] | ||
340 | fn generic_params() { | ||
341 | check_name_suggestion( | ||
342 | |e, _| from_method_call(e), | ||
343 | r#" | ||
344 | fn foo() { | ||
345 | $0bar.frobnicate::<i32, u32>()$0 | ||
346 | }"#, | ||
347 | "frobnicate", | ||
348 | ); | ||
349 | } | ||
350 | |||
351 | #[test] | ||
352 | fn to_name() { | ||
353 | check_name_suggestion( | ||
354 | |e, _| from_method_call(e), | ||
355 | r#" | ||
356 | struct Args; | ||
357 | struct Config; | ||
358 | impl Args { | ||
359 | fn to_config(&self) -> Config {} | ||
360 | } | ||
361 | fn foo() { | ||
362 | $0Args.to_config()$0; | ||
363 | }"#, | ||
364 | "config", | ||
365 | ); | ||
366 | } | ||
367 | } | ||
368 | |||
369 | mod from_param { | ||
370 | use crate::tests::check_name_suggestion_not_applicable; | ||
371 | |||
372 | use super::*; | ||
373 | |||
374 | #[test] | ||
375 | fn plain_func() { | ||
376 | check_name_suggestion( | ||
377 | from_param, | ||
378 | r#" | ||
379 | fn bar(n: i32, m: u32); | ||
380 | fn foo() { | ||
381 | bar($01$0, 2) | ||
382 | }"#, | ||
383 | "n", | ||
384 | ); | ||
385 | } | ||
386 | |||
387 | #[test] | ||
388 | fn mut_param() { | ||
389 | check_name_suggestion( | ||
390 | from_param, | ||
391 | r#" | ||
392 | fn bar(mut n: i32, m: u32); | ||
393 | fn foo() { | ||
394 | bar($01$0, 2) | ||
395 | }"#, | ||
396 | "n", | ||
397 | ); | ||
398 | } | ||
399 | |||
400 | #[test] | ||
401 | fn func_does_not_exist() { | ||
402 | check_name_suggestion_not_applicable( | ||
403 | from_param, | ||
404 | r#" | ||
405 | fn foo() { | ||
406 | bar($01$0, 2) | ||
407 | }"#, | ||
408 | ); | ||
409 | } | ||
410 | |||
411 | #[test] | ||
412 | fn unnamed_param() { | ||
413 | check_name_suggestion_not_applicable( | ||
414 | from_param, | ||
415 | r#" | ||
416 | fn bar(_: i32, m: u32); | ||
417 | fn foo() { | ||
418 | bar($01$0, 2) | ||
419 | }"#, | ||
420 | ); | ||
421 | } | ||
422 | |||
423 | #[test] | ||
424 | fn tuple_pat() { | ||
425 | check_name_suggestion_not_applicable( | ||
426 | from_param, | ||
427 | r#" | ||
428 | fn bar((n, k): (i32, i32), m: u32); | ||
429 | fn foo() { | ||
430 | bar($0(1, 2)$0, 3) | ||
431 | }"#, | ||
432 | ); | ||
433 | } | ||
434 | |||
435 | #[test] | ||
436 | fn ref_pat() { | ||
437 | check_name_suggestion( | ||
438 | from_param, | ||
439 | r#" | ||
440 | fn bar(&n: &i32, m: u32); | ||
441 | fn foo() { | ||
442 | bar($0&1$0, 3) | ||
443 | }"#, | ||
444 | "n", | ||
445 | ); | ||
446 | } | ||
447 | |||
448 | #[test] | ||
449 | fn box_pat() { | ||
450 | check_name_suggestion( | ||
451 | from_param, | ||
452 | r#" | ||
453 | fn bar(box n: &i32, m: u32); | ||
454 | fn foo() { | ||
455 | bar($01$0, 3) | ||
456 | }"#, | ||
457 | "n", | ||
458 | ); | ||
459 | } | ||
460 | |||
461 | #[test] | ||
462 | fn param_out_of_index() { | ||
463 | check_name_suggestion_not_applicable( | ||
464 | from_param, | ||
465 | r#" | ||
466 | fn bar(n: i32, m: u32); | ||
467 | fn foo() { | ||
468 | bar(1, 2, $03$0) | ||
469 | }"#, | ||
470 | ); | ||
471 | } | ||
472 | |||
473 | #[test] | ||
474 | fn generic_param_resolved() { | ||
475 | check_name_suggestion( | ||
476 | from_param, | ||
477 | r#" | ||
478 | fn bar<T>(n: T, m: u32); | ||
479 | fn foo() { | ||
480 | bar($01$0, 2) | ||
481 | }"#, | ||
482 | "n", | ||
483 | ); | ||
484 | } | ||
485 | |||
486 | #[test] | ||
487 | fn generic_param_unresolved() { | ||
488 | check_name_suggestion( | ||
489 | from_param, | ||
490 | r#" | ||
491 | fn bar<T>(n: T, m: u32); | ||
492 | fn foo<T>(x: T) { | ||
493 | bar($0x$0, 2) | ||
494 | }"#, | ||
495 | "n", | ||
496 | ); | ||
497 | } | ||
498 | |||
499 | #[test] | ||
500 | fn method() { | ||
501 | check_name_suggestion( | ||
502 | from_param, | ||
503 | r#" | ||
504 | struct S; | ||
505 | impl S { | ||
506 | fn bar(&self, n: i32, m: u32); | ||
507 | } | ||
508 | fn foo() { | ||
509 | S.bar($01$0, 2) | ||
510 | }"#, | ||
511 | "n", | ||
512 | ); | ||
513 | } | ||
514 | |||
515 | #[test] | ||
516 | fn method_ufcs() { | ||
517 | check_name_suggestion( | ||
518 | from_param, | ||
519 | r#" | ||
520 | struct S; | ||
521 | impl S { | ||
522 | fn bar(&self, n: i32, m: u32); | ||
523 | } | ||
524 | fn foo() { | ||
525 | S::bar(&S, $01$0, 2) | ||
526 | }"#, | ||
527 | "n", | ||
528 | ); | ||
529 | } | ||
530 | |||
531 | #[test] | ||
532 | fn method_self() { | ||
533 | check_name_suggestion_not_applicable( | ||
534 | from_param, | ||
535 | r#" | ||
536 | struct S; | ||
537 | impl S { | ||
538 | fn bar(&self, n: i32, m: u32); | ||
539 | } | ||
540 | fn foo() { | ||
541 | S::bar($0&S$0, 1, 2) | ||
542 | }"#, | ||
543 | ); | ||
544 | } | ||
545 | |||
546 | #[test] | ||
547 | fn method_self_named() { | ||
548 | check_name_suggestion( | ||
549 | from_param, | ||
550 | r#" | ||
551 | struct S; | ||
552 | impl S { | ||
553 | fn bar(strukt: &Self, n: i32, m: u32); | ||
554 | } | ||
555 | fn foo() { | ||
556 | S::bar($0&S$0, 1, 2) | ||
557 | }"#, | ||
558 | "strukt", | ||
559 | ); | ||
560 | } | ||
561 | } | ||
562 | |||
563 | mod from_type { | ||
564 | use crate::tests::check_name_suggestion_not_applicable; | ||
565 | |||
566 | use super::*; | ||
567 | |||
568 | #[test] | ||
569 | fn i32() { | ||
570 | check_name_suggestion_not_applicable( | ||
571 | from_type, | ||
572 | r#" | ||
573 | fn foo() { | ||
574 | let _: i32 = $01$0; | ||
575 | }"#, | ||
576 | ); | ||
577 | } | ||
578 | |||
579 | #[test] | ||
580 | fn u64() { | ||
581 | check_name_suggestion_not_applicable( | ||
582 | from_type, | ||
583 | r#" | ||
584 | fn foo() { | ||
585 | let _: u64 = $01$0; | ||
586 | }"#, | ||
587 | ); | ||
588 | } | ||
589 | |||
590 | #[test] | ||
591 | fn bool() { | ||
592 | check_name_suggestion_not_applicable( | ||
593 | from_type, | ||
594 | r#" | ||
595 | fn foo() { | ||
596 | let _: bool = $0true$0; | ||
597 | }"#, | ||
598 | ); | ||
599 | } | ||
600 | |||
601 | #[test] | ||
602 | fn struct_unit() { | ||
603 | check_name_suggestion( | ||
604 | from_type, | ||
605 | r#" | ||
606 | struct Seed; | ||
607 | fn foo() { | ||
608 | let _ = $0Seed$0; | ||
609 | }"#, | ||
610 | "seed", | ||
611 | ); | ||
612 | } | ||
613 | |||
614 | #[test] | ||
615 | fn struct_unit_to_snake() { | ||
616 | check_name_suggestion( | ||
617 | from_type, | ||
618 | r#" | ||
619 | struct SeedState; | ||
620 | fn foo() { | ||
621 | let _ = $0SeedState$0; | ||
622 | }"#, | ||
623 | "seed_state", | ||
624 | ); | ||
625 | } | ||
626 | |||
627 | #[test] | ||
628 | fn struct_single_arg() { | ||
629 | check_name_suggestion( | ||
630 | from_type, | ||
631 | r#" | ||
632 | struct Seed(u32); | ||
633 | fn foo() { | ||
634 | let _ = $0Seed(0)$0; | ||
635 | }"#, | ||
636 | "seed", | ||
637 | ); | ||
638 | } | ||
639 | |||
640 | #[test] | ||
641 | fn struct_with_fields() { | ||
642 | check_name_suggestion( | ||
643 | from_type, | ||
644 | r#" | ||
645 | struct Seed { value: u32 } | ||
646 | fn foo() { | ||
647 | let _ = $0Seed { value: 0 }$0; | ||
648 | }"#, | ||
649 | "seed", | ||
650 | ); | ||
651 | } | ||
652 | |||
653 | #[test] | ||
654 | fn enum_() { | ||
655 | check_name_suggestion( | ||
656 | from_type, | ||
657 | r#" | ||
658 | enum Kind { A, B } | ||
659 | fn foo() { | ||
660 | let _ = $0Kind::A$0; | ||
661 | }"#, | ||
662 | "kind", | ||
663 | ); | ||
664 | } | ||
665 | |||
666 | #[test] | ||
667 | fn enum_generic_resolved() { | ||
668 | check_name_suggestion( | ||
669 | from_type, | ||
670 | r#" | ||
671 | enum Kind<T> { A(T), B } | ||
672 | fn foo() { | ||
673 | let _ = $0Kind::A(1)$0; | ||
674 | }"#, | ||
675 | "kind", | ||
676 | ); | ||
677 | } | ||
678 | |||
679 | #[test] | ||
680 | fn enum_generic_unresolved() { | ||
681 | check_name_suggestion( | ||
682 | from_type, | ||
683 | r#" | ||
684 | enum Kind<T> { A(T), B } | ||
685 | fn foo<T>(x: T) { | ||
686 | let _ = $0Kind::A(x)$0; | ||
687 | }"#, | ||
688 | "kind", | ||
689 | ); | ||
690 | } | ||
691 | |||
692 | #[test] | ||
693 | fn dyn_trait() { | ||
694 | check_name_suggestion( | ||
695 | from_type, | ||
696 | r#" | ||
697 | trait DynHandler {} | ||
698 | fn bar() -> dyn DynHandler {} | ||
699 | fn foo() { | ||
700 | $0bar()$0; | ||
701 | }"#, | ||
702 | "dyn_handler", | ||
703 | ); | ||
704 | } | ||
705 | |||
706 | #[test] | ||
707 | fn impl_trait() { | ||
708 | check_name_suggestion( | ||
709 | from_type, | ||
710 | r#" | ||
711 | trait StaticHandler {} | ||
712 | fn bar() -> impl StaticHandler {} | ||
713 | fn foo() { | ||
714 | $0bar()$0; | ||
715 | }"#, | ||
716 | "static_handler", | ||
717 | ); | ||
718 | } | ||
719 | |||
720 | #[test] | ||
721 | fn impl_trait_plus_clone() { | ||
722 | check_name_suggestion( | ||
723 | from_type, | ||
724 | r#" | ||
725 | trait StaticHandler {} | ||
726 | trait Clone {} | ||
727 | fn bar() -> impl StaticHandler + Clone {} | ||
728 | fn foo() { | ||
729 | $0bar()$0; | ||
730 | }"#, | ||
731 | "static_handler", | ||
732 | ); | ||
733 | } | ||
734 | |||
735 | #[test] | ||
736 | fn impl_trait_plus_lifetime() { | ||
737 | check_name_suggestion( | ||
738 | from_type, | ||
739 | r#" | ||
740 | trait StaticHandler {} | ||
741 | trait Clone {} | ||
742 | fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {} | ||
743 | fn foo() { | ||
744 | $0bar(&1)$0; | ||
745 | }"#, | ||
746 | "static_handler", | ||
747 | ); | ||
748 | } | ||
749 | |||
750 | #[test] | ||
751 | fn impl_trait_plus_trait() { | ||
752 | check_name_suggestion_not_applicable( | ||
753 | from_type, | ||
754 | r#" | ||
755 | trait Handler {} | ||
756 | trait StaticHandler {} | ||
757 | fn bar() -> impl StaticHandler + Handler {} | ||
758 | fn foo() { | ||
759 | $0bar()$0; | ||
760 | }"#, | ||
761 | ); | ||
762 | } | ||
763 | |||
764 | #[test] | ||
765 | fn ref_value() { | ||
766 | check_name_suggestion( | ||
767 | from_type, | ||
768 | r#" | ||
769 | struct Seed; | ||
770 | fn bar() -> &Seed {} | ||
771 | fn foo() { | ||
772 | $0bar()$0; | ||
773 | }"#, | ||
774 | "seed", | ||
775 | ); | ||
776 | } | ||
777 | |||
778 | #[test] | ||
779 | fn box_value() { | ||
780 | check_name_suggestion( | ||
781 | from_type, | ||
782 | r#" | ||
783 | struct Box<T>(*const T); | ||
784 | struct Seed; | ||
785 | fn bar() -> Box<Seed> {} | ||
786 | fn foo() { | ||
787 | $0bar()$0; | ||
788 | }"#, | ||
789 | "seed", | ||
790 | ); | ||
791 | } | ||
792 | |||
793 | #[test] | ||
794 | fn box_generic() { | ||
795 | check_name_suggestion_not_applicable( | ||
796 | from_type, | ||
797 | r#" | ||
798 | struct Box<T>(*const T); | ||
799 | fn bar<T>() -> Box<T> {} | ||
800 | fn foo<T>() { | ||
801 | $0bar::<T>()$0; | ||
802 | }"#, | ||
803 | ); | ||
804 | } | ||
805 | |||
806 | #[test] | ||
807 | fn option_value() { | ||
808 | check_name_suggestion( | ||
809 | from_type, | ||
810 | r#" | ||
811 | enum Option<T> { Some(T) } | ||
812 | struct Seed; | ||
813 | fn bar() -> Option<Seed> {} | ||
814 | fn foo() { | ||
815 | $0bar()$0; | ||
816 | }"#, | ||
817 | "seed", | ||
818 | ); | ||
819 | } | ||
820 | |||
821 | #[test] | ||
822 | fn result_value() { | ||
823 | check_name_suggestion( | ||
824 | from_type, | ||
825 | r#" | ||
826 | enum Result<T, E> { Ok(T), Err(E) } | ||
827 | struct Seed; | ||
828 | struct Error; | ||
829 | fn bar() -> Result<Seed, Error> {} | ||
830 | fn foo() { | ||
831 | $0bar()$0; | ||
832 | }"#, | ||
833 | "seed", | ||
834 | ); | ||
835 | } | ||
836 | } | ||
837 | |||
838 | mod variable { | ||
839 | use super::*; | ||
840 | |||
841 | #[test] | ||
842 | fn ref_call() { | ||
843 | check_name_suggestion( | ||
844 | |e, c| Some(variable(e, c)), | ||
845 | r#" | ||
846 | fn foo() { | ||
847 | $0&bar(1, 3)$0 | ||
848 | }"#, | ||
849 | "bar", | ||
850 | ); | ||
851 | } | ||
852 | |||
853 | #[test] | ||
854 | fn name_to_string() { | ||
855 | check_name_suggestion( | ||
856 | |e, c| Some(variable(e, c)), | ||
857 | r#" | ||
858 | fn foo() { | ||
859 | $0function.name().to_string()$0 | ||
860 | }"#, | ||
861 | "name", | ||
862 | ); | ||
863 | } | ||
864 | |||
865 | #[test] | ||
866 | fn nested_useless_method() { | ||
867 | check_name_suggestion( | ||
868 | |e, c| Some(variable(e, c)), | ||
869 | r#" | ||
870 | fn foo() { | ||
871 | $0function.name().as_ref().unwrap().to_string()$0 | ||
872 | }"#, | ||
873 | "name", | ||
874 | ); | ||
875 | } | ||
876 | } | ||
877 | } | ||