]> git.lizzy.rs Git - rust.git/commitdiff
Initial implementation of the #5085 issue
authorAleksei Sidorov <gorthauer87@yandex.ru>
Wed, 2 Sep 2020 22:32:18 +0000 (01:32 +0300)
committerAleksei Sidorov <gorthauer87@yandex.ru>
Thu, 3 Sep 2020 11:47:07 +0000 (14:47 +0300)
crates/assists/src/handlers/replace_impl_trait_with_generic.rs [new file with mode: 0644]
crates/assists/src/lib.rs
crates/syntax/src/ast/edit.rs
crates/syntax/src/ast/make.rs

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 (file)
index 0000000..8af2d16
--- /dev/null
@@ -0,0 +1,62 @@
+use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: replace_impl_trait_with_generic
+//
+// Replaces `impl Trait` function argument with the named generic.
+pub(crate) fn replace_impl_trait_with_generic(
+    acc: &mut Assists,
+    ctx: &AssistContext,
+) -> Option<()> {
+    let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
+    let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?;
+    let type_fn = type_param.syntax().ancestors().nth(2).and_then(ast::Fn::cast)?;
+
+    let generic_param_list =
+        type_fn.generic_param_list().unwrap_or_else(|| make::generic_param_list(None));
+
+    let impl_trait_ty = type_impl_trait
+        .syntax()
+        .descendants()
+        .last()
+        .and_then(ast::NameRef::cast)?
+        .text()
+        .to_string();
+
+    let target = type_fn.syntax().text_range();
+    acc.add(
+        AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
+        "Replace impl trait with generic",
+        target,
+        |edit| {
+            let generic_letter = impl_trait_ty[..1].to_string();
+            edit.replace_ast::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter));
+
+            let new_params = generic_param_list
+                .append_param(make::generic_param(generic_letter, Some(impl_trait_ty)));
+            let new_type_fn = type_fn.replace_descendant(generic_param_list, new_params);
+            edit.replace_ast(type_fn.clone(), new_type_fn);
+        },
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::tests::check_assist;
+
+    #[test]
+    fn replace_with_generic_params() {
+        check_assist(
+            replace_impl_trait_with_generic,
+            r#"
+            fn foo<G>(bar: <|>impl Bar) {}
+            "#,
+            r#"
+            fn foo<G, B: Bar>(bar: B) {}
+            "#,
+        );
+    }
+}
index 2e0d191a609deb5c063091b28464fbc868ef3d49..cbac53e71112e28582dae079ce7c58ef046ee750 100644 (file)
@@ -155,6 +155,7 @@ mod handlers {
     mod remove_unused_param;
     mod reorder_fields;
     mod replace_if_let_with_match;
+    mod replace_impl_trait_with_generic;
     mod replace_let_with_if_let;
     mod replace_qualified_name_with_use;
     mod replace_unwrap_with_match;
@@ -202,6 +203,7 @@ pub(crate) fn all() -> &'static [Handler] {
             remove_unused_param::remove_unused_param,
             reorder_fields::reorder_fields,
             replace_if_let_with_match::replace_if_let_with_match,
+            replace_impl_trait_with_generic::replace_impl_trait_with_generic,
             replace_let_with_if_let::replace_let_with_if_let,
             replace_qualified_name_with_use::replace_qualified_name_with_use,
             replace_unwrap_with_match::replace_unwrap_with_match,
index 8234753332666562d2725daf29a066f4b2e7be82..1ccb4de6af8e23f6c4be4aecb57614961d85897e 100644 (file)
@@ -459,6 +459,72 @@ pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
     }
 }
 
+impl ast::GenericParamList {
+    #[must_use]
+    pub fn append_params(&self, params: impl IntoIterator<Item = ast::GenericParam>) -> Self {
+        let mut res = self.clone();
+        params.into_iter().for_each(|it| res = res.append_param(it));
+        res
+    }
+
+    #[must_use]
+    pub fn append_param(&self, item: ast::GenericParam) -> Self {
+        let is_multiline = self.syntax().text().contains_char('\n');
+        let ws;
+        let space = if is_multiline {
+            ws = tokens::WsBuilder::new(&format!(
+                "\n{}    ",
+                leading_indent(self.syntax()).unwrap_or_default()
+            ));
+            ws.ws()
+        } else {
+            tokens::single_space()
+        };
+
+        let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
+        to_insert.push(space.into());
+        to_insert.push(item.syntax().clone().into());
+        to_insert.push(make::token(T![,]).into());
+
+        macro_rules! after_l_angle {
+            () => {{
+                let anchor = match self.l_angle_token() {
+                    Some(it) => it.into(),
+                    None => return self.clone(),
+                };
+                InsertPosition::After(anchor)
+            }};
+        }
+
+        macro_rules! after_field {
+            ($anchor:expr) => {
+                if let Some(comma) = $anchor
+                    .syntax()
+                    .siblings_with_tokens(Direction::Next)
+                    .find(|it| it.kind() == T![,])
+                {
+                    InsertPosition::After(comma)
+                } else {
+                    to_insert.insert(0, make::token(T![,]).into());
+                    InsertPosition::After($anchor.syntax().clone().into())
+                }
+            };
+        };
+
+        if !is_multiline {
+            // don't insert comma before angle
+            to_insert.pop();
+        }
+
+        let position = match self.generic_params().last() {
+            Some(it) => after_field!(it),
+            None => after_l_angle!(),
+        };
+
+        self.insert_children(position, to_insert)
+    }
+}
+
 #[must_use]
 pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
     N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
index c2c938ad11d11f16928a9b4c11639afce599560e..7329e3039c5c46afce6ebff57ab320e6b62da575 100644 (file)
@@ -294,6 +294,21 @@ pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList
     ast_from_text(&format!("fn f({}) {{ }}", args))
 }
 
+pub fn generic_param(name: String, ty: Option<String>) -> ast::GenericParam {
+    let bound = match ty {
+        Some(it) => format!(": {}", it),
+        None => String::new(),
+    };
+    ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound))
+}
+
+pub fn generic_param_list(
+    pats: impl IntoIterator<Item = ast::GenericParam>,
+) -> ast::GenericParamList {
+    let args = pats.into_iter().join(", ");
+    ast_from_text(&format!("fn f<{}>() {{ }}", args))
+}
+
 pub fn visibility_pub_crate() -> ast::Visibility {
     ast_from_text("pub(crate) struct S")
 }