diff options
-rw-r--r-- | crates/assists/src/handlers/replace_impl_trait_with_generic.rs | 168 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 13 | ||||
-rw-r--r-- | crates/syntax/src/ast/edit.rs | 70 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 15 |
5 files changed, 267 insertions, 1 deletions
diff --git a/crates/assists/src/handlers/replace_impl_trait_with_generic.rs b/crates/assists/src/handlers/replace_impl_trait_with_generic.rs new file mode 100644 index 000000000..6738bc134 --- /dev/null +++ b/crates/assists/src/handlers/replace_impl_trait_with_generic.rs | |||
@@ -0,0 +1,168 @@ | |||
1 | use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner}; | ||
2 | |||
3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
4 | |||
5 | // Assist: replace_impl_trait_with_generic | ||
6 | // | ||
7 | // Replaces `impl Trait` function argument with the named generic. | ||
8 | // | ||
9 | // ``` | ||
10 | // fn foo(bar: <|>impl Bar) {} | ||
11 | // ``` | ||
12 | // -> | ||
13 | // ``` | ||
14 | // fn foo<B: Bar>(bar: B) {} | ||
15 | // ``` | ||
16 | pub(crate) fn replace_impl_trait_with_generic( | ||
17 | acc: &mut Assists, | ||
18 | ctx: &AssistContext, | ||
19 | ) -> Option<()> { | ||
20 | let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?; | ||
21 | let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?; | ||
22 | let type_fn = type_param.syntax().ancestors().find_map(ast::Fn::cast)?; | ||
23 | |||
24 | let impl_trait_ty = type_impl_trait.type_bound_list()?; | ||
25 | |||
26 | let target = type_fn.syntax().text_range(); | ||
27 | acc.add( | ||
28 | AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite), | ||
29 | "Replace impl trait with generic", | ||
30 | target, | ||
31 | |edit| { | ||
32 | let generic_letter = impl_trait_ty.to_string().chars().next().unwrap().to_string(); | ||
33 | |||
34 | let generic_param_list = type_fn | ||
35 | .generic_param_list() | ||
36 | .unwrap_or_else(|| make::generic_param_list(None)) | ||
37 | .append_param(make::generic_param(generic_letter.clone(), Some(impl_trait_ty))); | ||
38 | |||
39 | let new_type_fn = type_fn | ||
40 | .replace_descendant::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter)) | ||
41 | .with_generic_param_list(generic_param_list); | ||
42 | |||
43 | edit.replace_ast(type_fn.clone(), new_type_fn); | ||
44 | }, | ||
45 | ) | ||
46 | } | ||
47 | |||
48 | #[cfg(test)] | ||
49 | mod tests { | ||
50 | use super::*; | ||
51 | |||
52 | use crate::tests::check_assist; | ||
53 | |||
54 | #[test] | ||
55 | fn replace_impl_trait_with_generic_params() { | ||
56 | check_assist( | ||
57 | replace_impl_trait_with_generic, | ||
58 | r#" | ||
59 | fn foo<G>(bar: <|>impl Bar) {} | ||
60 | "#, | ||
61 | r#" | ||
62 | fn foo<G, B: Bar>(bar: B) {} | ||
63 | "#, | ||
64 | ); | ||
65 | } | ||
66 | |||
67 | #[test] | ||
68 | fn replace_impl_trait_without_generic_params() { | ||
69 | check_assist( | ||
70 | replace_impl_trait_with_generic, | ||
71 | r#" | ||
72 | fn foo(bar: <|>impl Bar) {} | ||
73 | "#, | ||
74 | r#" | ||
75 | fn foo<B: Bar>(bar: B) {} | ||
76 | "#, | ||
77 | ); | ||
78 | } | ||
79 | |||
80 | #[test] | ||
81 | fn replace_two_impl_trait_with_generic_params() { | ||
82 | check_assist( | ||
83 | replace_impl_trait_with_generic, | ||
84 | r#" | ||
85 | fn foo<G>(foo: impl Foo, bar: <|>impl Bar) {} | ||
86 | "#, | ||
87 | r#" | ||
88 | fn foo<G, B: Bar>(foo: impl Foo, bar: B) {} | ||
89 | "#, | ||
90 | ); | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn replace_impl_trait_with_empty_generic_params() { | ||
95 | check_assist( | ||
96 | replace_impl_trait_with_generic, | ||
97 | r#" | ||
98 | fn foo<>(bar: <|>impl Bar) {} | ||
99 | "#, | ||
100 | r#" | ||
101 | fn foo<B: Bar>(bar: B) {} | ||
102 | "#, | ||
103 | ); | ||
104 | } | ||
105 | |||
106 | #[test] | ||
107 | fn replace_impl_trait_with_empty_multiline_generic_params() { | ||
108 | check_assist( | ||
109 | replace_impl_trait_with_generic, | ||
110 | r#" | ||
111 | fn foo< | ||
112 | >(bar: <|>impl Bar) {} | ||
113 | "#, | ||
114 | r#" | ||
115 | fn foo<B: Bar | ||
116 | >(bar: B) {} | ||
117 | "#, | ||
118 | ); | ||
119 | } | ||
120 | |||
121 | #[test] | ||
122 | #[ignore = "This case is very rare but there is no simple solutions to fix it."] | ||
123 | fn replace_impl_trait_with_exist_generic_letter() { | ||
124 | check_assist( | ||
125 | replace_impl_trait_with_generic, | ||
126 | r#" | ||
127 | fn foo<B>(bar: <|>impl Bar) {} | ||
128 | "#, | ||
129 | r#" | ||
130 | fn foo<B, C: Bar>(bar: C) {} | ||
131 | "#, | ||
132 | ); | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn replace_impl_trait_with_multiline_generic_params() { | ||
137 | check_assist( | ||
138 | replace_impl_trait_with_generic, | ||
139 | r#" | ||
140 | fn foo< | ||
141 | G: Foo, | ||
142 | F, | ||
143 | H, | ||
144 | >(bar: <|>impl Bar) {} | ||
145 | "#, | ||
146 | r#" | ||
147 | fn foo< | ||
148 | G: Foo, | ||
149 | F, | ||
150 | H, B: Bar | ||
151 | >(bar: B) {} | ||
152 | "#, | ||
153 | ); | ||
154 | } | ||
155 | |||
156 | #[test] | ||
157 | fn replace_impl_trait_multiple() { | ||
158 | check_assist( | ||
159 | replace_impl_trait_with_generic, | ||
160 | r#" | ||
161 | fn foo(bar: <|>impl Foo + Bar) {} | ||
162 | "#, | ||
163 | r#" | ||
164 | fn foo<F: Foo + Bar>(bar: F) {} | ||
165 | "#, | ||
166 | ); | ||
167 | } | ||
168 | } | ||
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 2e0d191a6..cbac53e71 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -155,6 +155,7 @@ mod handlers { | |||
155 | mod remove_unused_param; | 155 | mod remove_unused_param; |
156 | mod reorder_fields; | 156 | mod reorder_fields; |
157 | mod replace_if_let_with_match; | 157 | mod replace_if_let_with_match; |
158 | mod replace_impl_trait_with_generic; | ||
158 | mod replace_let_with_if_let; | 159 | mod replace_let_with_if_let; |
159 | mod replace_qualified_name_with_use; | 160 | mod replace_qualified_name_with_use; |
160 | mod replace_unwrap_with_match; | 161 | mod replace_unwrap_with_match; |
@@ -202,6 +203,7 @@ mod handlers { | |||
202 | remove_unused_param::remove_unused_param, | 203 | remove_unused_param::remove_unused_param, |
203 | reorder_fields::reorder_fields, | 204 | reorder_fields::reorder_fields, |
204 | replace_if_let_with_match::replace_if_let_with_match, | 205 | replace_if_let_with_match::replace_if_let_with_match, |
206 | replace_impl_trait_with_generic::replace_impl_trait_with_generic, | ||
205 | replace_let_with_if_let::replace_let_with_if_let, | 207 | replace_let_with_if_let::replace_let_with_if_let, |
206 | replace_qualified_name_with_use::replace_qualified_name_with_use, | 208 | replace_qualified_name_with_use::replace_qualified_name_with_use, |
207 | replace_unwrap_with_match::replace_unwrap_with_match, | 209 | replace_unwrap_with_match::replace_unwrap_with_match, |
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index 04c8fd1f9..27d15adb0 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -815,6 +815,19 @@ fn handle(action: Action) { | |||
815 | } | 815 | } |
816 | 816 | ||
817 | #[test] | 817 | #[test] |
818 | fn doctest_replace_impl_trait_with_generic() { | ||
819 | check_doc_test( | ||
820 | "replace_impl_trait_with_generic", | ||
821 | r#####" | ||
822 | fn foo(bar: <|>impl Bar) {} | ||
823 | "#####, | ||
824 | r#####" | ||
825 | fn foo<B: Bar>(bar: B) {} | ||
826 | "#####, | ||
827 | ) | ||
828 | } | ||
829 | |||
830 | #[test] | ||
818 | fn doctest_replace_let_with_if_let() { | 831 | fn doctest_replace_let_with_if_let() { |
819 | check_doc_test( | 832 | check_doc_test( |
820 | "replace_let_with_if_let", | 833 | "replace_let_with_if_let", |
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs index 823475333..8b1c65dd6 100644 --- a/crates/syntax/src/ast/edit.rs +++ b/crates/syntax/src/ast/edit.rs | |||
@@ -13,7 +13,7 @@ use crate::{ | |||
13 | ast::{ | 13 | ast::{ |
14 | self, | 14 | self, |
15 | make::{self, tokens}, | 15 | make::{self, tokens}, |
16 | AstNode, TypeBoundsOwner, | 16 | AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner, |
17 | }, | 17 | }, |
18 | AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind, | 18 | AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind, |
19 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | 19 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, |
@@ -46,6 +46,19 @@ impl ast::Fn { | |||
46 | to_insert.push(body.syntax().clone().into()); | 46 | to_insert.push(body.syntax().clone().into()); |
47 | self.replace_children(single_node(old_body_or_semi), to_insert) | 47 | self.replace_children(single_node(old_body_or_semi), to_insert) |
48 | } | 48 | } |
49 | |||
50 | #[must_use] | ||
51 | pub fn with_generic_param_list(&self, generic_args: ast::GenericParamList) -> ast::Fn { | ||
52 | if let Some(old) = self.generic_param_list() { | ||
53 | return self.replace_descendant(old, generic_args); | ||
54 | } | ||
55 | |||
56 | let anchor = self.name().expect("The function must have a name").syntax().clone(); | ||
57 | |||
58 | let mut to_insert: ArrayVec<[SyntaxElement; 1]> = ArrayVec::new(); | ||
59 | to_insert.push(generic_args.syntax().clone().into()); | ||
60 | self.insert_children(InsertPosition::After(anchor.into()), to_insert) | ||
61 | } | ||
49 | } | 62 | } |
50 | 63 | ||
51 | fn make_multiline<N>(node: N) -> N | 64 | fn make_multiline<N>(node: N) -> N |
@@ -459,6 +472,61 @@ impl ast::MatchArmList { | |||
459 | } | 472 | } |
460 | } | 473 | } |
461 | 474 | ||
475 | impl ast::GenericParamList { | ||
476 | #[must_use] | ||
477 | pub fn append_params( | ||
478 | &self, | ||
479 | params: impl IntoIterator<Item = ast::GenericParam>, | ||
480 | ) -> ast::GenericParamList { | ||
481 | let mut res = self.clone(); | ||
482 | params.into_iter().for_each(|it| res = res.append_param(it)); | ||
483 | res | ||
484 | } | ||
485 | |||
486 | #[must_use] | ||
487 | pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList { | ||
488 | let space = tokens::single_space(); | ||
489 | |||
490 | let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); | ||
491 | if self.generic_params().next().is_some() { | ||
492 | to_insert.push(space.into()); | ||
493 | } | ||
494 | to_insert.push(item.syntax().clone().into()); | ||
495 | |||
496 | macro_rules! after_l_angle { | ||
497 | () => {{ | ||
498 | let anchor = match self.l_angle_token() { | ||
499 | Some(it) => it.into(), | ||
500 | None => return self.clone(), | ||
501 | }; | ||
502 | InsertPosition::After(anchor) | ||
503 | }}; | ||
504 | } | ||
505 | |||
506 | macro_rules! after_field { | ||
507 | ($anchor:expr) => { | ||
508 | if let Some(comma) = $anchor | ||
509 | .syntax() | ||
510 | .siblings_with_tokens(Direction::Next) | ||
511 | .find(|it| it.kind() == T![,]) | ||
512 | { | ||
513 | InsertPosition::After(comma) | ||
514 | } else { | ||
515 | to_insert.insert(0, make::token(T![,]).into()); | ||
516 | InsertPosition::After($anchor.syntax().clone().into()) | ||
517 | } | ||
518 | }; | ||
519 | }; | ||
520 | |||
521 | let position = match self.generic_params().last() { | ||
522 | Some(it) => after_field!(it), | ||
523 | None => after_l_angle!(), | ||
524 | }; | ||
525 | |||
526 | self.insert_children(position, to_insert) | ||
527 | } | ||
528 | } | ||
529 | |||
462 | #[must_use] | 530 | #[must_use] |
463 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { | 531 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { |
464 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() | 532 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() |
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 33f1ad7b3..25e8a359d 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs | |||
@@ -294,6 +294,21 @@ pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList | |||
294 | ast_from_text(&format!("fn f({}) {{ }}", args)) | 294 | ast_from_text(&format!("fn f({}) {{ }}", args)) |
295 | } | 295 | } |
296 | 296 | ||
297 | pub fn generic_param(name: String, ty: Option<ast::TypeBoundList>) -> ast::GenericParam { | ||
298 | let bound = match ty { | ||
299 | Some(it) => format!(": {}", it), | ||
300 | None => String::new(), | ||
301 | }; | ||
302 | ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound)) | ||
303 | } | ||
304 | |||
305 | pub fn generic_param_list( | ||
306 | pats: impl IntoIterator<Item = ast::GenericParam>, | ||
307 | ) -> ast::GenericParamList { | ||
308 | let args = pats.into_iter().join(", "); | ||
309 | ast_from_text(&format!("fn f<{}>() {{ }}", args)) | ||
310 | } | ||
311 | |||
297 | pub fn visibility_pub_crate() -> ast::Visibility { | 312 | pub fn visibility_pub_crate() -> ast::Visibility { |
298 | ast_from_text("pub(crate) struct S") | 313 | ast_from_text("pub(crate) struct S") |
299 | } | 314 | } |