Skip to content

Commit 77ce295

Browse files
committed
Auto merge of rust-lang#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
2 parents eea61bd + 1aba000 commit 77ce295

File tree

3 files changed

+282
-0
lines changed

3 files changed

+282
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait};
2+
use itertools::Itertools;
3+
use syntax::{
4+
ast::{self, make, AstNode, HasName},
5+
ted,
6+
};
7+
8+
use crate::{AssistContext, AssistId, AssistKind, Assists};
9+
10+
// Assist: convert_from_to_tryfrom
11+
//
12+
// Converts a From impl to a TryFrom impl, wrapping returns in `Ok`.
13+
//
14+
// ```
15+
// # //- minicore: from
16+
// impl $0From<usize> for Thing {
17+
// fn from(val: usize) -> Self {
18+
// Thing {
19+
// b: val.to_string(),
20+
// a: val
21+
// }
22+
// }
23+
// }
24+
// ```
25+
// ->
26+
// ```
27+
// impl TryFrom<usize> for Thing {
28+
// type Error = ${0:()};
29+
//
30+
// fn try_from(val: usize) -> Result<Self, Self::Error> {
31+
// Ok(Thing {
32+
// b: val.to_string(),
33+
// a: val
34+
// })
35+
// }
36+
// }
37+
// ```
38+
pub(crate) fn convert_from_to_tryfrom(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
39+
let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
40+
let trait_ty = impl_.trait_()?;
41+
42+
let module = ctx.sema.scope(impl_.syntax())?.module();
43+
44+
let from_type = match &trait_ty {
45+
ast::Type::PathType(path) => {
46+
path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
47+
}
48+
_ => return None,
49+
};
50+
51+
let associated_items = impl_.assoc_item_list()?;
52+
let from_fn = associated_items.assoc_items().find_map(|item| {
53+
if let ast::AssocItem::Fn(f) = item {
54+
if f.name()?.text() == "from" {
55+
return Some(f);
56+
}
57+
};
58+
None
59+
})?;
60+
61+
let from_fn_name = from_fn.name()?;
62+
let from_fn_return_type = from_fn.ret_type()?.ty()?;
63+
64+
let return_exprs = from_fn.body()?.syntax().descendants().filter_map(ast::ReturnExpr::cast);
65+
let tail_expr = from_fn.body()?.tail_expr()?;
66+
67+
if resolve_target_trait(&ctx.sema, &impl_)?
68+
!= FamousDefs(&ctx.sema, module.krate()).core_convert_From()?
69+
{
70+
return None;
71+
}
72+
73+
acc.add(
74+
AssistId("convert_from_to_tryfrom", AssistKind::RefactorRewrite),
75+
"Convert From to TryFrom",
76+
impl_.syntax().text_range(),
77+
|builder| {
78+
let trait_ty = builder.make_mut(trait_ty);
79+
let from_fn_return_type = builder.make_mut(from_fn_return_type);
80+
let from_fn_name = builder.make_mut(from_fn_name);
81+
let tail_expr = builder.make_mut(tail_expr);
82+
let return_exprs = return_exprs.map(|r| builder.make_mut(r)).collect_vec();
83+
let associated_items = builder.make_mut(associated_items).clone();
84+
85+
ted::replace(
86+
trait_ty.syntax(),
87+
make::ty(&format!("TryFrom<{from_type}>")).syntax().clone_for_update(),
88+
);
89+
ted::replace(
90+
from_fn_return_type.syntax(),
91+
make::ty("Result<Self, Self::Error>").syntax().clone_for_update(),
92+
);
93+
ted::replace(from_fn_name.syntax(), make::name("try_from").syntax().clone_for_update());
94+
ted::replace(
95+
tail_expr.syntax(),
96+
wrap_ok(tail_expr.clone()).syntax().clone_for_update(),
97+
);
98+
99+
for r in return_exprs {
100+
let t = r.expr().unwrap_or_else(make::expr_unit);
101+
ted::replace(t.syntax(), wrap_ok(t.clone()).syntax().clone_for_update());
102+
}
103+
104+
let error_type = ast::AssocItem::TypeAlias(make::ty_alias(
105+
"Error",
106+
None,
107+
None,
108+
None,
109+
Some((make::ty_unit(), None)),
110+
))
111+
.clone_for_update();
112+
113+
if let Some(cap) = ctx.config.snippet_cap {
114+
if let ast::AssocItem::TypeAlias(type_alias) = &error_type {
115+
if let Some(ty) = type_alias.ty() {
116+
builder.add_placeholder_snippet(cap, ty);
117+
}
118+
}
119+
}
120+
121+
associated_items.add_item_at_start(error_type);
122+
},
123+
)
124+
}
125+
126+
fn wrap_ok(expr: ast::Expr) -> ast::Expr {
127+
make::expr_call(
128+
make::expr_path(make::ext::ident_path("Ok")),
129+
make::arg_list(std::iter::once(expr)),
130+
)
131+
}
132+
133+
#[cfg(test)]
134+
mod tests {
135+
use super::*;
136+
137+
use crate::tests::{check_assist, check_assist_not_applicable};
138+
139+
#[test]
140+
fn converts_from_to_tryfrom() {
141+
check_assist(
142+
convert_from_to_tryfrom,
143+
r#"
144+
//- minicore: from
145+
struct Foo(String);
146+
147+
impl $0From<String> for Foo {
148+
fn from(val: String) -> Self {
149+
if val == "bar" {
150+
return Foo(val);
151+
}
152+
Self(val)
153+
}
154+
}
155+
"#,
156+
r#"
157+
struct Foo(String);
158+
159+
impl TryFrom<String> for Foo {
160+
type Error = ${0:()};
161+
162+
fn try_from(val: String) -> Result<Self, Self::Error> {
163+
if val == "bar" {
164+
return Ok(Foo(val));
165+
}
166+
Ok(Self(val))
167+
}
168+
}
169+
"#,
170+
);
171+
}
172+
173+
#[test]
174+
fn converts_from_to_tryfrom_nested_type() {
175+
check_assist(
176+
convert_from_to_tryfrom,
177+
r#"
178+
//- minicore: from
179+
struct Foo(String);
180+
181+
impl $0From<Option<String>> for Foo {
182+
fn from(val: Option<String>) -> Self {
183+
match val {
184+
Some(val) => Foo(val),
185+
None => Foo("".to_string())
186+
}
187+
}
188+
}
189+
"#,
190+
r#"
191+
struct Foo(String);
192+
193+
impl TryFrom<Option<String>> for Foo {
194+
type Error = ${0:()};
195+
196+
fn try_from(val: Option<String>) -> Result<Self, Self::Error> {
197+
Ok(match val {
198+
Some(val) => Foo(val),
199+
None => Foo("".to_string())
200+
})
201+
}
202+
}
203+
"#,
204+
);
205+
}
206+
207+
#[test]
208+
fn converts_from_to_tryfrom_preserves_lifetimes() {
209+
check_assist(
210+
convert_from_to_tryfrom,
211+
r#"
212+
//- minicore: from
213+
struct Foo<'a>(&'a str);
214+
215+
impl<'a> $0From<&'a str> for Foo<'a> {
216+
fn from(val: &'a str) -> Self {
217+
Self(val)
218+
}
219+
}
220+
"#,
221+
r#"
222+
struct Foo<'a>(&'a str);
223+
224+
impl<'a> TryFrom<&'a str> for Foo<'a> {
225+
type Error = ${0:()};
226+
227+
fn try_from(val: &'a str) -> Result<Self, Self::Error> {
228+
Ok(Self(val))
229+
}
230+
}
231+
"#,
232+
);
233+
}
234+
235+
#[test]
236+
fn other_trait_not_applicable() {
237+
check_assist_not_applicable(
238+
convert_from_to_tryfrom,
239+
r#"
240+
struct Foo(String);
241+
242+
impl $0TryFrom<String> for Foo {
243+
fn try_from(val: String) -> Result<Self, Self::Error> {
244+
Ok(Self(val))
245+
}
246+
}
247+
"#,
248+
);
249+
}
250+
}

crates/ide-assists/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ mod handlers {
116116
mod change_visibility;
117117
mod convert_bool_then;
118118
mod convert_comment_block;
119+
mod convert_from_to_tryfrom;
119120
mod convert_integer_literal;
120121
mod convert_into_to_from;
121122
mod convert_iter_for_each_to_for;
@@ -238,6 +239,7 @@ mod handlers {
238239
convert_bool_then::convert_bool_then_to_if,
239240
convert_bool_then::convert_if_to_bool_then,
240241
convert_comment_block::convert_comment_block,
242+
convert_from_to_tryfrom::convert_from_to_tryfrom,
241243
convert_integer_literal::convert_integer_literal,
242244
convert_into_to_from::convert_into_to_from,
243245
convert_iter_for_each_to_for::convert_iter_for_each_to_for,

crates/ide-assists/src/tests/generated.rs

+30
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,36 @@ fn main() {
390390
)
391391
}
392392

393+
#[test]
394+
fn doctest_convert_from_to_tryfrom() {
395+
check_doc_test(
396+
"convert_from_to_tryfrom",
397+
r#####"
398+
//- minicore: from
399+
impl $0From<usize> for Thing {
400+
fn from(val: usize) -> Self {
401+
Thing {
402+
b: val.to_string(),
403+
a: val
404+
}
405+
}
406+
}
407+
"#####,
408+
r#####"
409+
impl TryFrom<usize> for Thing {
410+
type Error = ${0:()};
411+
412+
fn try_from(val: usize) -> Result<Self, Self::Error> {
413+
Ok(Thing {
414+
b: val.to_string(),
415+
a: val
416+
})
417+
}
418+
}
419+
"#####,
420+
)
421+
}
422+
393423
#[test]
394424
fn doctest_convert_if_to_bool_then() {
395425
check_doc_test(

0 commit comments

Comments
 (0)