]> git.lizzy.rs Git - rust.git/commitdiff
Convert Into to From assist
authorGraeme Coupar <grambo@grambo.me.uk>
Fri, 2 Apr 2021 13:00:56 +0000 (14:00 +0100)
committerGraeme Coupar <grambo@grambo.me.uk>
Sat, 3 Apr 2021 14:48:35 +0000 (15:48 +0100)
This adds a "Convert Into to From" assist, useful since clippy has
recently started adding lints on every `Into`.

It covers converting the signature, and converting any `self`/`Self`
references within the body to the correct types.

It does assume that every instance of `Into` can be converted to a
`From`, which I _think_ is the case now.  Let me know if there's
something I'm not thinking of and I can try and make it smarter.

crates/ide_assists/src/handlers/convert_into_to_from.rs [new file with mode: 0644]
crates/ide_assists/src/lib.rs
crates/ide_assists/src/tests/generated.rs
crates/ide_db/src/helpers.rs
crates/ide_db/src/helpers/famous_defs_fixture.rs

diff --git a/crates/ide_assists/src/handlers/convert_into_to_from.rs b/crates/ide_assists/src/handlers/convert_into_to_from.rs
new file mode 100644 (file)
index 0000000..199e1ad
--- /dev/null
@@ -0,0 +1,355 @@
+use ide_db::{
+    helpers::{mod_path_to_ast, FamousDefs},
+    traits::resolve_target_trait,
+};
+use syntax::ast::{self, AstNode, NameOwner};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: convert_into_to_from
+//
+// Converts an Into impl to an equivalent From impl.
+//
+// ```
+// # //- /lib.rs crate:core
+// # pub mod convert { pub trait Into<T> { pub fn into(self) -> T; } }
+// # //- /lib.rs crate:main deps:core
+// # use core::convert::Into;
+// impl $0Into<Thing> for usize {
+//     fn into(self) -> Thing {
+//         Thing {
+//             b: self.to_string(),
+//             a: self
+//         }
+//     }
+// }
+// ```
+// ->
+// ```
+// # use core::convert::Into;
+// impl From<usize> for Thing {
+//     fn from(val: usize) -> Self {
+//         Thing {
+//             b: val.to_string(),
+//             a: val
+//         }
+//     }
+// }
+// ```
+pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
+    let src_type = impl_.self_ty()?;
+    let ast_trait = impl_.trait_()?;
+
+    let module = ctx.sema.scope(impl_.syntax()).module()?;
+
+    let trait_ = resolve_target_trait(&ctx.sema, &impl_)?;
+    if trait_ != FamousDefs(&ctx.sema, Some(module.krate())).core_convert_Into()? {
+        return None;
+    }
+
+    let src_type_path = {
+        let src_type_path = src_type.syntax().descendants().find_map(ast::Path::cast)?;
+        let src_type_def = match ctx.sema.resolve_path(&src_type_path) {
+            Some(hir::PathResolution::Def(module_def)) => module_def,
+            _ => return None,
+        };
+
+        mod_path_to_ast(&module.find_use_path(ctx.db(), src_type_def)?)
+    };
+
+    let dest_type = match &ast_trait {
+        ast::Type::PathType(path) => {
+            path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
+        }
+        _ => return None,
+    };
+
+    let into_fn = impl_.assoc_item_list()?.assoc_items().find_map(|item| {
+        if let ast::AssocItem::Fn(f) = item {
+            if f.name()?.text() == "into" {
+                return Some(f);
+            }
+        };
+        None
+    })?;
+
+    let into_fn_name = into_fn.name()?;
+    let into_fn_params = into_fn.param_list()?;
+    let into_fn_return = into_fn.ret_type()?;
+
+    let selfs = into_fn
+        .body()?
+        .syntax()
+        .descendants()
+        .filter_map(ast::NameRef::cast)
+        .filter(|name| name.text() == "self" || name.text() == "Self");
+
+    acc.add(
+        AssistId("convert_into_to_from", AssistKind::RefactorRewrite),
+        "Convert Into to From",
+        impl_.syntax().text_range(),
+        |builder| {
+            builder.replace(src_type.syntax().text_range(), dest_type.to_string());
+            builder.replace(ast_trait.syntax().text_range(), format!("From<{}>", src_type));
+            builder.replace(into_fn_return.syntax().text_range(), "-> Self");
+            builder.replace(
+                into_fn_params.syntax().text_range(),
+                format!("(val: {})", src_type.to_string()),
+            );
+            builder.replace(into_fn_name.syntax().text_range(), "from");
+
+            for s in selfs {
+                match s.text().as_ref() {
+                    "self" => builder.replace(s.syntax().text_range(), "val"),
+                    "Self" => builder.replace(s.syntax().text_range(), src_type_path.to_string()),
+                    _ => {}
+                }
+            }
+        },
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::tests::check_assist;
+
+    #[test]
+    fn convert_into_to_from_converts_a_struct() {
+        check_convert_into_to_from(
+            r#"
+struct Thing {
+    a: String,
+    b: usize
+}
+
+impl $0core::convert::Into<Thing> for usize {
+    fn into(self) -> Thing {
+        Thing {
+            b: self.to_string(),
+            a: self
+        }
+    }
+}
+"#,
+            r#"
+struct Thing {
+    a: String,
+    b: usize
+}
+
+impl From<usize> for Thing {
+    fn from(val: usize) -> Self {
+        Thing {
+            b: val.to_string(),
+            a: val
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn convert_into_to_from_converts_enums() {
+        check_convert_into_to_from(
+            r#"
+enum Thing {
+    Foo(String),
+    Bar(String)
+}
+
+impl $0core::convert::Into<String> for Thing {
+    fn into(self) -> String {
+        match self {
+            Self::Foo(s) => s,
+            Self::Bar(s) => s
+        }
+    }
+}
+"#,
+            r#"
+enum Thing {
+    Foo(String),
+    Bar(String)
+}
+
+impl From<Thing> for String {
+    fn from(val: Thing) -> Self {
+        match val {
+            Thing::Foo(s) => s,
+            Thing::Bar(s) => s
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn convert_into_to_from_on_enum_with_lifetimes() {
+        check_convert_into_to_from(
+            r#"
+enum Thing<'a> {
+    Foo(&'a str),
+    Bar(&'a str)
+}
+
+impl<'a> $0core::convert::Into<&'a str> for Thing<'a> {
+    fn into(self) -> &'a str {
+        match self {
+            Self::Foo(s) => s,
+            Self::Bar(s) => s
+        }
+    }
+}
+"#,
+            r#"
+enum Thing<'a> {
+    Foo(&'a str),
+    Bar(&'a str)
+}
+
+impl<'a> From<Thing<'a>> for &'a str {
+    fn from(val: Thing<'a>) -> Self {
+        match val {
+            Thing::Foo(s) => s,
+            Thing::Bar(s) => s
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn convert_into_to_from_works_on_references() {
+        check_convert_into_to_from(
+            r#"
+struct Thing(String);
+
+impl $0core::convert::Into<String> for &Thing {
+    fn into(self) -> Thing {
+        self.0.clone()
+    }
+}
+"#,
+            r#"
+struct Thing(String);
+
+impl From<&Thing> for String {
+    fn from(val: &Thing) -> Self {
+        val.0.clone()
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn convert_into_to_from_works_on_qualified_structs() {
+        check_convert_into_to_from(
+            r#"
+mod things {
+    pub struct Thing(String);
+    pub struct BetterThing(String);
+}
+
+impl $0core::convert::Into<things::BetterThing> for &things::Thing {
+    fn into(self) -> Thing {
+        things::BetterThing(self.0.clone())
+    }
+}
+"#,
+            r#"
+mod things {
+    pub struct Thing(String);
+    pub struct BetterThing(String);
+}
+
+impl From<&things::Thing> for things::BetterThing {
+    fn from(val: &things::Thing) -> Self {
+        things::BetterThing(val.0.clone())
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn convert_into_to_from_works_on_qualified_enums() {
+        check_convert_into_to_from(
+            r#"
+mod things {
+    pub enum Thing {
+        A(String)
+    }
+    pub struct BetterThing {
+        B(String)
+    }
+}
+
+impl $0core::convert::Into<things::BetterThing> for &things::Thing {
+    fn into(self) -> Thing {
+        match self {
+            Self::A(s) => things::BetterThing::B(s)
+        }
+    }
+}
+"#,
+            r#"
+mod things {
+    pub enum Thing {
+        A(String)
+    }
+    pub struct BetterThing {
+        B(String)
+    }
+}
+
+impl From<&things::Thing> for things::BetterThing {
+    fn from(val: &things::Thing) -> Self {
+        match val {
+            things::Thing::A(s) => things::BetterThing::B(s)
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn convert_into_to_from_not_applicable_on_any_trait_named_into() {
+        check_assist_not_applicable(
+            r#"
+pub trait Into<T> {{
+    pub fn into(self) -> T;
+}}
+
+struct Thing {
+    a: String,
+}
+
+impl $0Into<Thing> for String {
+    fn into(self) -> Thing {
+        Thing {
+            a: self
+        }
+    }
+}
+"#,
+        );
+    }
+
+    fn check_convert_into_to_from(before: &str, after: &str) {
+        let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
+        check_assist(convert_into_to_from, before, after);
+    }
+
+    fn check_assist_not_applicable(before: &str) {
+        let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
+        crate::tests::check_assist_not_applicable(convert_into_to_from, before);
+    }
+}
index 3d1dcef4ceadaf0751706dd262f2a2aa0d441b96..3e2c82dace0a718347afd4317a799a9966bc9d6f 100644 (file)
@@ -117,6 +117,7 @@ mod handlers {
     mod convert_integer_literal;
     mod convert_comment_block;
     mod convert_iter_for_each_to_for;
+    mod convert_into_to_from;
     mod early_return;
     mod expand_glob_import;
     mod extract_function;
@@ -185,6 +186,7 @@ pub(crate) fn all() -> &'static [Handler] {
             convert_integer_literal::convert_integer_literal,
             convert_comment_block::convert_comment_block,
             convert_iter_for_each_to_for::convert_iter_for_each_to_for,
+            convert_into_to_from::convert_into_to_from,
             early_return::convert_to_guarded_return,
             expand_glob_import::expand_glob_import,
             extract_struct_from_enum_variant::extract_struct_from_enum_variant,
index 03b7fb36623193d2a06115b24803623831b75985..27a22ca10c18566f7908f8163f77521762df693d 100644 (file)
@@ -205,6 +205,38 @@ fn doctest_convert_integer_literal() {
     )
 }
 
+#[test]
+fn doctest_convert_into_to_from() {
+    check_doc_test(
+        "convert_into_to_from",
+        r#####"
+//- /lib.rs crate:core
+pub mod convert { pub trait Into<T> { pub fn into(self) -> T; } }
+//- /lib.rs crate:main deps:core
+use core::convert::Into;
+impl $0Into<Thing> for usize {
+    fn into(self) -> Thing {
+        Thing {
+            b: self.to_string(),
+            a: self
+        }
+    }
+}
+"#####,
+        r#####"
+use core::convert::Into;
+impl From<usize> for Thing {
+    fn from(val: usize) -> Self {
+        Thing {
+            b: val.to_string(),
+            a: val
+        }
+    }
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_convert_iter_for_each_to_for() {
     check_doc_test(
index 9992a92bdf20ac7edad870d44f3048f3a7a73136..66798ea3a69aa310d50d50d4d5f9a187457da640 100644 (file)
@@ -93,6 +93,10 @@ pub fn core_convert_From(&self) -> Option<Trait> {
         self.find_trait("core:convert:From")
     }
 
+    pub fn core_convert_Into(&self) -> Option<Trait> {
+        self.find_trait("core:convert:Into")
+    }
+
     pub fn core_option_Option(&self) -> Option<Enum> {
         self.find_enum("core:option:Option")
     }
index d3464ae17b95ba4bda3852415c994af1393f78cf..4d79e064e081dae76900cbbc4249762a5e42b3da 100644 (file)
@@ -14,6 +14,10 @@ pub mod convert {
     pub trait From<T> {
         fn from(t: T) -> Self;
     }
+
+    pub trait Into<T> {
+        pub fn into(self) -> T;
+    }
 }
 
 pub mod default {
@@ -120,7 +124,7 @@ pub enum Option<T> {
 pub mod prelude {
     pub use crate::{
         cmp::Ord,
-        convert::From,
+        convert::{From, Into},
         default::Default,
         iter::{IntoIterator, Iterator},
         ops::{Fn, FnMut, FnOnce},