From ee0384901784b2cbe8d62f259f8598cc0fc7d306 Mon Sep 17 00:00:00 2001 From: Graeme Coupar Date: Fri, 2 Apr 2021 14:00:56 +0100 Subject: Convert Into to From assist 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. --- .../src/handlers/convert_into_to_from.rs | 355 +++++++++++++++++++++ crates/ide_assists/src/lib.rs | 2 + crates/ide_assists/src/tests/generated.rs | 32 ++ 3 files changed, 389 insertions(+) create mode 100644 crates/ide_assists/src/handlers/convert_into_to_from.rs (limited to 'crates/ide_assists') 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 index 000000000..199e1ad5c --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_into_to_from.rs @@ -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 { pub fn into(self) -> T; } } +// # //- /lib.rs crate:main deps:core +// # use core::convert::Into; +// impl $0Into for usize { +// fn into(self) -> Thing { +// Thing { +// b: self.to_string(), +// a: self +// } +// } +// } +// ``` +// -> +// ``` +// # use core::convert::Into; +// impl From 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::()?; + 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 for usize { + fn into(self) -> Thing { + Thing { + b: self.to_string(), + a: self + } + } +} +"#, + r#" +struct Thing { + a: String, + b: usize +} + +impl From 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 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 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> 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 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 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 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 {{ + pub fn into(self) -> T; +}} + +struct Thing { + a: String, +} + +impl $0Into 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); + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 3d1dcef4c..3e2c82dac 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -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 @@ mod handlers { 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, diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 03b7fb366..27a22ca10 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -205,6 +205,38 @@ const _: i32 = 0b1010; ) } +#[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 { pub fn into(self) -> T; } } +//- /lib.rs crate:main deps:core +use core::convert::Into; +impl $0Into for usize { + fn into(self) -> Thing { + Thing { + b: self.to_string(), + a: self + } + } +} +"#####, + r#####" +use core::convert::Into; +impl From 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( -- cgit v1.2.3