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