mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-03 10:33:34 +00:00
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.
This commit is contained in:
parent
71ef64b673
commit
ee03849017
355
crates/ide_assists/src/handlers/convert_into_to_from.rs
Normal file
355
crates/ide_assists/src/handlers/convert_into_to_from.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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<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(
|
||||
|
@ -93,6 +93,10 @@ impl FamousDefs<'_, '_> {
|
||||
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")
|
||||
}
|
||||
|
@ -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 mod option {
|
||||
pub mod prelude {
|
||||
pub use crate::{
|
||||
cmp::Ord,
|
||||
convert::From,
|
||||
convert::{From, Into},
|
||||
default::Default,
|
||||
iter::{IntoIterator, Iterator},
|
||||
ops::{Fn, FnMut, FnOnce},
|
||||
|
Loading…
Reference in New Issue
Block a user