mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 05:53:45 +00:00
Auto merge of #17094 - Lindronics:convert-from-to-tryfrom, r=Veykril
feat: Add convert From to TryFrom assist Adds a new code assist to convert a `From` impl into a `TryFrom` impl. This is useful in situations where it turns out after or halfway through writing a `From` implementation that the conversion is actually fallible. ## Example https://github.com/rust-lang/rust-analyzer/assets/26360861/872ec7c4-c9ff-451c-9453-4baaaad47326
This commit is contained in:
commit
77ce295006
3 changed files with 282 additions and 0 deletions
250
crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs
Normal file
250
crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs
Normal file
|
@ -0,0 +1,250 @@
|
|||
use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait};
|
||||
use itertools::Itertools;
|
||||
use syntax::{
|
||||
ast::{self, make, AstNode, HasName},
|
||||
ted,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: convert_from_to_tryfrom
|
||||
//
|
||||
// Converts a From impl to a TryFrom impl, wrapping returns in `Ok`.
|
||||
//
|
||||
// ```
|
||||
// # //- minicore: from
|
||||
// impl $0From<usize> for Thing {
|
||||
// fn from(val: usize) -> Self {
|
||||
// Thing {
|
||||
// b: val.to_string(),
|
||||
// a: val
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// impl TryFrom<usize> for Thing {
|
||||
// type Error = ${0:()};
|
||||
//
|
||||
// fn try_from(val: usize) -> Result<Self, Self::Error> {
|
||||
// Ok(Thing {
|
||||
// b: val.to_string(),
|
||||
// a: val
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn convert_from_to_tryfrom(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
|
||||
let trait_ty = impl_.trait_()?;
|
||||
|
||||
let module = ctx.sema.scope(impl_.syntax())?.module();
|
||||
|
||||
let from_type = match &trait_ty {
|
||||
ast::Type::PathType(path) => {
|
||||
path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let associated_items = impl_.assoc_item_list()?;
|
||||
let from_fn = associated_items.assoc_items().find_map(|item| {
|
||||
if let ast::AssocItem::Fn(f) = item {
|
||||
if f.name()?.text() == "from" {
|
||||
return Some(f);
|
||||
}
|
||||
};
|
||||
None
|
||||
})?;
|
||||
|
||||
let from_fn_name = from_fn.name()?;
|
||||
let from_fn_return_type = from_fn.ret_type()?.ty()?;
|
||||
|
||||
let return_exprs = from_fn.body()?.syntax().descendants().filter_map(ast::ReturnExpr::cast);
|
||||
let tail_expr = from_fn.body()?.tail_expr()?;
|
||||
|
||||
if resolve_target_trait(&ctx.sema, &impl_)?
|
||||
!= FamousDefs(&ctx.sema, module.krate()).core_convert_From()?
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
acc.add(
|
||||
AssistId("convert_from_to_tryfrom", AssistKind::RefactorRewrite),
|
||||
"Convert From to TryFrom",
|
||||
impl_.syntax().text_range(),
|
||||
|builder| {
|
||||
let trait_ty = builder.make_mut(trait_ty);
|
||||
let from_fn_return_type = builder.make_mut(from_fn_return_type);
|
||||
let from_fn_name = builder.make_mut(from_fn_name);
|
||||
let tail_expr = builder.make_mut(tail_expr);
|
||||
let return_exprs = return_exprs.map(|r| builder.make_mut(r)).collect_vec();
|
||||
let associated_items = builder.make_mut(associated_items).clone();
|
||||
|
||||
ted::replace(
|
||||
trait_ty.syntax(),
|
||||
make::ty(&format!("TryFrom<{from_type}>")).syntax().clone_for_update(),
|
||||
);
|
||||
ted::replace(
|
||||
from_fn_return_type.syntax(),
|
||||
make::ty("Result<Self, Self::Error>").syntax().clone_for_update(),
|
||||
);
|
||||
ted::replace(from_fn_name.syntax(), make::name("try_from").syntax().clone_for_update());
|
||||
ted::replace(
|
||||
tail_expr.syntax(),
|
||||
wrap_ok(tail_expr.clone()).syntax().clone_for_update(),
|
||||
);
|
||||
|
||||
for r in return_exprs {
|
||||
let t = r.expr().unwrap_or_else(make::expr_unit);
|
||||
ted::replace(t.syntax(), wrap_ok(t.clone()).syntax().clone_for_update());
|
||||
}
|
||||
|
||||
let error_type = ast::AssocItem::TypeAlias(make::ty_alias(
|
||||
"Error",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some((make::ty_unit(), None)),
|
||||
))
|
||||
.clone_for_update();
|
||||
|
||||
if let Some(cap) = ctx.config.snippet_cap {
|
||||
if let ast::AssocItem::TypeAlias(type_alias) = &error_type {
|
||||
if let Some(ty) = type_alias.ty() {
|
||||
builder.add_placeholder_snippet(cap, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
associated_items.add_item_at_start(error_type);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn wrap_ok(expr: ast::Expr) -> ast::Expr {
|
||||
make::expr_call(
|
||||
make::expr_path(make::ext::ident_path("Ok")),
|
||||
make::arg_list(std::iter::once(expr)),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
#[test]
|
||||
fn converts_from_to_tryfrom() {
|
||||
check_assist(
|
||||
convert_from_to_tryfrom,
|
||||
r#"
|
||||
//- minicore: from
|
||||
struct Foo(String);
|
||||
|
||||
impl $0From<String> for Foo {
|
||||
fn from(val: String) -> Self {
|
||||
if val == "bar" {
|
||||
return Foo(val);
|
||||
}
|
||||
Self(val)
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo(String);
|
||||
|
||||
impl TryFrom<String> for Foo {
|
||||
type Error = ${0:()};
|
||||
|
||||
fn try_from(val: String) -> Result<Self, Self::Error> {
|
||||
if val == "bar" {
|
||||
return Ok(Foo(val));
|
||||
}
|
||||
Ok(Self(val))
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_from_to_tryfrom_nested_type() {
|
||||
check_assist(
|
||||
convert_from_to_tryfrom,
|
||||
r#"
|
||||
//- minicore: from
|
||||
struct Foo(String);
|
||||
|
||||
impl $0From<Option<String>> for Foo {
|
||||
fn from(val: Option<String>) -> Self {
|
||||
match val {
|
||||
Some(val) => Foo(val),
|
||||
None => Foo("".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo(String);
|
||||
|
||||
impl TryFrom<Option<String>> for Foo {
|
||||
type Error = ${0:()};
|
||||
|
||||
fn try_from(val: Option<String>) -> Result<Self, Self::Error> {
|
||||
Ok(match val {
|
||||
Some(val) => Foo(val),
|
||||
None => Foo("".to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_from_to_tryfrom_preserves_lifetimes() {
|
||||
check_assist(
|
||||
convert_from_to_tryfrom,
|
||||
r#"
|
||||
//- minicore: from
|
||||
struct Foo<'a>(&'a str);
|
||||
|
||||
impl<'a> $0From<&'a str> for Foo<'a> {
|
||||
fn from(val: &'a str) -> Self {
|
||||
Self(val)
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo<'a>(&'a str);
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Foo<'a> {
|
||||
type Error = ${0:()};
|
||||
|
||||
fn try_from(val: &'a str) -> Result<Self, Self::Error> {
|
||||
Ok(Self(val))
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_trait_not_applicable() {
|
||||
check_assist_not_applicable(
|
||||
convert_from_to_tryfrom,
|
||||
r#"
|
||||
struct Foo(String);
|
||||
|
||||
impl $0TryFrom<String> for Foo {
|
||||
fn try_from(val: String) -> Result<Self, Self::Error> {
|
||||
Ok(Self(val))
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -116,6 +116,7 @@ mod handlers {
|
|||
mod change_visibility;
|
||||
mod convert_bool_then;
|
||||
mod convert_comment_block;
|
||||
mod convert_from_to_tryfrom;
|
||||
mod convert_integer_literal;
|
||||
mod convert_into_to_from;
|
||||
mod convert_iter_for_each_to_for;
|
||||
|
@ -238,6 +239,7 @@ mod handlers {
|
|||
convert_bool_then::convert_bool_then_to_if,
|
||||
convert_bool_then::convert_if_to_bool_then,
|
||||
convert_comment_block::convert_comment_block,
|
||||
convert_from_to_tryfrom::convert_from_to_tryfrom,
|
||||
convert_integer_literal::convert_integer_literal,
|
||||
convert_into_to_from::convert_into_to_from,
|
||||
convert_iter_for_each_to_for::convert_iter_for_each_to_for,
|
||||
|
|
|
@ -390,6 +390,36 @@ fn main() {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_convert_from_to_tryfrom() {
|
||||
check_doc_test(
|
||||
"convert_from_to_tryfrom",
|
||||
r#####"
|
||||
//- minicore: from
|
||||
impl $0From<usize> for Thing {
|
||||
fn from(val: usize) -> Self {
|
||||
Thing {
|
||||
b: val.to_string(),
|
||||
a: val
|
||||
}
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
impl TryFrom<usize> for Thing {
|
||||
type Error = ${0:()};
|
||||
|
||||
fn try_from(val: usize) -> Result<Self, Self::Error> {
|
||||
Ok(Thing {
|
||||
b: val.to_string(),
|
||||
a: val
|
||||
})
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_convert_if_to_bool_then() {
|
||||
check_doc_test(
|
||||
|
|
Loading…
Reference in a new issue