diff options
-rw-r--r-- | crates/assists/src/handlers/replace_impl_trait_with_generic.rs | 62 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/syntax/src/ast/edit.rs | 66 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 15 |
4 files changed, 145 insertions, 0 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..8af2d16dd --- /dev/null +++ b/crates/assists/src/handlers/replace_impl_trait_with_generic.rs | |||
@@ -0,0 +1,62 @@ | |||
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 | pub(crate) fn replace_impl_trait_with_generic( | ||
9 | acc: &mut Assists, | ||
10 | ctx: &AssistContext, | ||
11 | ) -> Option<()> { | ||
12 | let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?; | ||
13 | let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?; | ||
14 | let type_fn = type_param.syntax().ancestors().nth(2).and_then(ast::Fn::cast)?; | ||
15 | |||
16 | let generic_param_list = | ||
17 | type_fn.generic_param_list().unwrap_or_else(|| make::generic_param_list(None)); | ||
18 | |||
19 | let impl_trait_ty = type_impl_trait | ||
20 | .syntax() | ||
21 | .descendants() | ||
22 | .last() | ||
23 | .and_then(ast::NameRef::cast)? | ||
24 | .text() | ||
25 | .to_string(); | ||
26 | |||
27 | let target = type_fn.syntax().text_range(); | ||
28 | acc.add( | ||
29 | AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite), | ||
30 | "Replace impl trait with generic", | ||
31 | target, | ||
32 | |edit| { | ||
33 | let generic_letter = impl_trait_ty[..1].to_string(); | ||
34 | edit.replace_ast::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter)); | ||
35 | |||
36 | let new_params = generic_param_list | ||
37 | .append_param(make::generic_param(generic_letter, Some(impl_trait_ty))); | ||
38 | let new_type_fn = type_fn.replace_descendant(generic_param_list, new_params); | ||
39 | edit.replace_ast(type_fn.clone(), new_type_fn); | ||
40 | }, | ||
41 | ) | ||
42 | } | ||
43 | |||
44 | #[cfg(test)] | ||
45 | mod tests { | ||
46 | use super::*; | ||
47 | |||
48 | use crate::tests::check_assist; | ||
49 | |||
50 | #[test] | ||
51 | fn replace_with_generic_params() { | ||
52 | check_assist( | ||
53 | replace_impl_trait_with_generic, | ||
54 | r#" | ||
55 | fn foo<G>(bar: <|>impl Bar) {} | ||
56 | "#, | ||
57 | r#" | ||
58 | fn foo<G, B: Bar>(bar: B) {} | ||
59 | "#, | ||
60 | ); | ||
61 | } | ||
62 | } | ||
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/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs index 823475333..1ccb4de6a 100644 --- a/crates/syntax/src/ast/edit.rs +++ b/crates/syntax/src/ast/edit.rs | |||
@@ -459,6 +459,72 @@ impl ast::MatchArmList { | |||
459 | } | 459 | } |
460 | } | 460 | } |
461 | 461 | ||
462 | impl ast::GenericParamList { | ||
463 | #[must_use] | ||
464 | pub fn append_params(&self, params: impl IntoIterator<Item = ast::GenericParam>) -> Self { | ||
465 | let mut res = self.clone(); | ||
466 | params.into_iter().for_each(|it| res = res.append_param(it)); | ||
467 | res | ||
468 | } | ||
469 | |||
470 | #[must_use] | ||
471 | pub fn append_param(&self, item: ast::GenericParam) -> Self { | ||
472 | let is_multiline = self.syntax().text().contains_char('\n'); | ||
473 | let ws; | ||
474 | let space = if is_multiline { | ||
475 | ws = tokens::WsBuilder::new(&format!( | ||
476 | "\n{} ", | ||
477 | leading_indent(self.syntax()).unwrap_or_default() | ||
478 | )); | ||
479 | ws.ws() | ||
480 | } else { | ||
481 | tokens::single_space() | ||
482 | }; | ||
483 | |||
484 | let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new(); | ||
485 | to_insert.push(space.into()); | ||
486 | to_insert.push(item.syntax().clone().into()); | ||
487 | to_insert.push(make::token(T![,]).into()); | ||
488 | |||
489 | macro_rules! after_l_angle { | ||
490 | () => {{ | ||
491 | let anchor = match self.l_angle_token() { | ||
492 | Some(it) => it.into(), | ||
493 | None => return self.clone(), | ||
494 | }; | ||
495 | InsertPosition::After(anchor) | ||
496 | }}; | ||
497 | } | ||
498 | |||
499 | macro_rules! after_field { | ||
500 | ($anchor:expr) => { | ||
501 | if let Some(comma) = $anchor | ||
502 | .syntax() | ||
503 | .siblings_with_tokens(Direction::Next) | ||
504 | .find(|it| it.kind() == T![,]) | ||
505 | { | ||
506 | InsertPosition::After(comma) | ||
507 | } else { | ||
508 | to_insert.insert(0, make::token(T![,]).into()); | ||
509 | InsertPosition::After($anchor.syntax().clone().into()) | ||
510 | } | ||
511 | }; | ||
512 | }; | ||
513 | |||
514 | if !is_multiline { | ||
515 | // don't insert comma before angle | ||
516 | to_insert.pop(); | ||
517 | } | ||
518 | |||
519 | let position = match self.generic_params().last() { | ||
520 | Some(it) => after_field!(it), | ||
521 | None => after_l_angle!(), | ||
522 | }; | ||
523 | |||
524 | self.insert_children(position, to_insert) | ||
525 | } | ||
526 | } | ||
527 | |||
462 | #[must_use] | 528 | #[must_use] |
463 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { | 529 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { |
464 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() | 530 | 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 c2c938ad1..7329e3039 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<String>) -> 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 | } |