aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/replace_impl_trait_with_generic.rs62
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/syntax/src/ast/edit.rs66
-rw-r--r--crates/syntax/src/ast/make.rs15
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 @@
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.
8pub(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)]
45mod 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
462impl 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]
463pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 529pub 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
297pub 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
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}