aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/replace_impl_trait_with_generic.rs168
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs13
-rw-r--r--crates/syntax/src/ast/edit.rs70
-rw-r--r--crates/syntax/src/ast/make.rs15
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 @@
1use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner};
2
3use 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// ```
16pub(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)]
49mod 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]
818fn doctest_replace_impl_trait_with_generic() {
819 check_doc_test(
820 "replace_impl_trait_with_generic",
821 r#####"
822fn foo(bar: <|>impl Bar) {}
823"#####,
824 r#####"
825fn foo<B: Bar>(bar: B) {}
826"#####,
827 )
828}
829
830#[test]
818fn doctest_replace_let_with_if_let() { 831fn 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
51fn make_multiline<N>(node: N) -> N 64fn make_multiline<N>(node: N) -> N
@@ -459,6 +472,61 @@ impl ast::MatchArmList {
459 } 472 }
460} 473}
461 474
475impl 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]
463pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 531pub 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
297pub 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
305pub 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
297pub fn visibility_pub_crate() -> ast::Visibility { 312pub fn visibility_pub_crate() -> ast::Visibility {
298 ast_from_text("pub(crate) struct S") 313 ast_from_text("pub(crate) struct S")
299} 314}