ide_assists/handlers/
convert_into_to_from.rs1use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast, traits::resolve_target_trait};
2use syntax::ast::{self, AstNode, HasGenericArgs, HasName};
3
4use crate::{AssistContext, AssistId, Assists};
5
6pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
35 let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
36 let src_type = impl_.self_ty()?;
37 let ast_trait = impl_.trait_()?;
38
39 let module = ctx.sema.scope(impl_.syntax())?.module();
40
41 let trait_ = resolve_target_trait(&ctx.sema, &impl_)?;
42 if trait_ != FamousDefs(&ctx.sema, module.krate(ctx.db())).core_convert_Into()? {
43 return None;
44 }
45
46 let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
47
48 let src_type_path = {
49 let src_type_path = src_type.syntax().descendants().find_map(ast::Path::cast)?;
50 let src_type_def = match ctx.sema.resolve_path(&src_type_path) {
51 Some(hir::PathResolution::Def(module_def)) => module_def,
52 _ => return None,
53 };
54 mod_path_to_ast(
55 &module.find_path(ctx.db(), src_type_def, cfg)?,
56 module.krate(ctx.db()).edition(ctx.db()),
57 )
58 };
59
60 let dest_type = match &ast_trait {
61 ast::Type::PathType(path) => {
62 path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
63 }
64 _ => return None,
65 };
66
67 let into_fn = impl_.assoc_item_list()?.assoc_items().find_map(|item| {
68 if let ast::AssocItem::Fn(f) = item
69 && f.name()?.text() == "into"
70 {
71 return Some(f);
72 };
73 None
74 })?;
75
76 let into_fn_name = into_fn.name()?;
77 let into_fn_params = into_fn.param_list()?;
78 let into_fn_return = into_fn.ret_type()?;
79
80 let selfs = into_fn
81 .body()?
82 .syntax()
83 .descendants()
84 .filter_map(ast::NameRef::cast)
85 .filter(|name| name.text() == "self" || name.text() == "Self");
86
87 acc.add(
88 AssistId::refactor_rewrite("convert_into_to_from"),
89 "Convert Into to From",
90 impl_.syntax().text_range(),
91 |builder| {
92 builder.replace(src_type.syntax().text_range(), dest_type.to_string());
93 builder.replace(ast_trait.syntax().text_range(), format!("From<{src_type}>"));
94 builder.replace(into_fn_return.syntax().text_range(), "-> Self");
95 builder.replace(into_fn_params.syntax().text_range(), format!("(val: {src_type})"));
96 builder.replace(into_fn_name.syntax().text_range(), "from");
97
98 for s in selfs {
99 match s.text().as_ref() {
100 "self" => builder.replace(s.syntax().text_range(), "val"),
101 "Self" => builder.replace(s.syntax().text_range(), src_type_path.to_string()),
102 _ => {}
103 }
104 }
105 },
106 )
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 use crate::tests::{check_assist, check_assist_not_applicable};
114
115 #[test]
116 fn convert_into_to_from_converts_a_struct() {
117 check_assist(
118 convert_into_to_from,
119 r#"
120//- minicore: from
121struct Thing {
122 a: String,
123 b: usize
124}
125
126impl $0core::convert::Into<Thing> for usize {
127 fn into(self) -> Thing {
128 Thing {
129 b: self.to_string(),
130 a: self
131 }
132 }
133}
134"#,
135 r#"
136struct Thing {
137 a: String,
138 b: usize
139}
140
141impl From<usize> for Thing {
142 fn from(val: usize) -> Self {
143 Thing {
144 b: val.to_string(),
145 a: val
146 }
147 }
148}
149"#,
150 )
151 }
152
153 #[test]
154 fn convert_into_to_from_converts_enums() {
155 check_assist(
156 convert_into_to_from,
157 r#"
158//- minicore: from
159enum Thing {
160 Foo(String),
161 Bar(String)
162}
163
164impl $0core::convert::Into<String> for Thing {
165 fn into(self) -> String {
166 match self {
167 Self::Foo(s) => s,
168 Self::Bar(s) => s
169 }
170 }
171}
172"#,
173 r#"
174enum Thing {
175 Foo(String),
176 Bar(String)
177}
178
179impl From<Thing> for String {
180 fn from(val: Thing) -> Self {
181 match val {
182 Thing::Foo(s) => s,
183 Thing::Bar(s) => s
184 }
185 }
186}
187"#,
188 )
189 }
190
191 #[test]
192 fn convert_into_to_from_on_enum_with_lifetimes() {
193 check_assist(
194 convert_into_to_from,
195 r#"
196//- minicore: from
197enum Thing<'a> {
198 Foo(&'a str),
199 Bar(&'a str)
200}
201
202impl<'a> $0core::convert::Into<&'a str> for Thing<'a> {
203 fn into(self) -> &'a str {
204 match self {
205 Self::Foo(s) => s,
206 Self::Bar(s) => s
207 }
208 }
209}
210"#,
211 r#"
212enum Thing<'a> {
213 Foo(&'a str),
214 Bar(&'a str)
215}
216
217impl<'a> From<Thing<'a>> for &'a str {
218 fn from(val: Thing<'a>) -> Self {
219 match val {
220 Thing::Foo(s) => s,
221 Thing::Bar(s) => s
222 }
223 }
224}
225"#,
226 )
227 }
228
229 #[test]
230 fn convert_into_to_from_works_on_references() {
231 check_assist(
232 convert_into_to_from,
233 r#"
234//- minicore: from
235struct Thing(String);
236
237impl $0core::convert::Into<String> for &Thing {
238 fn into(self) -> Thing {
239 self.0.clone()
240 }
241}
242"#,
243 r#"
244struct Thing(String);
245
246impl From<&Thing> for String {
247 fn from(val: &Thing) -> Self {
248 val.0.clone()
249 }
250}
251"#,
252 )
253 }
254
255 #[test]
256 fn convert_into_to_from_works_on_qualified_structs() {
257 check_assist(
258 convert_into_to_from,
259 r#"
260//- minicore: from
261mod things {
262 pub struct Thing(String);
263 pub struct BetterThing(String);
264}
265
266impl $0core::convert::Into<things::BetterThing> for &things::Thing {
267 fn into(self) -> Thing {
268 things::BetterThing(self.0.clone())
269 }
270}
271"#,
272 r#"
273mod things {
274 pub struct Thing(String);
275 pub struct BetterThing(String);
276}
277
278impl From<&things::Thing> for things::BetterThing {
279 fn from(val: &things::Thing) -> Self {
280 things::BetterThing(val.0.clone())
281 }
282}
283"#,
284 )
285 }
286
287 #[test]
288 fn convert_into_to_from_works_on_qualified_enums() {
289 check_assist(
290 convert_into_to_from,
291 r#"
292//- minicore: from
293mod things {
294 pub enum Thing {
295 A(String)
296 }
297 pub struct BetterThing {
298 B(String)
299 }
300}
301
302impl $0core::convert::Into<things::BetterThing> for &things::Thing {
303 fn into(self) -> Thing {
304 match self {
305 Self::A(s) => things::BetterThing::B(s)
306 }
307 }
308}
309"#,
310 r#"
311mod things {
312 pub enum Thing {
313 A(String)
314 }
315 pub struct BetterThing {
316 B(String)
317 }
318}
319
320impl From<&things::Thing> for things::BetterThing {
321 fn from(val: &things::Thing) -> Self {
322 match val {
323 things::Thing::A(s) => things::BetterThing::B(s)
324 }
325 }
326}
327"#,
328 )
329 }
330
331 #[test]
332 fn convert_into_to_from_not_applicable_on_any_trait_named_into() {
333 check_assist_not_applicable(
334 convert_into_to_from,
335 r#"
336//- minicore: from
337pub trait Into<T> {
338 pub fn into(self) -> T;
339}
340
341struct Thing {
342 a: String,
343}
344
345impl $0Into<Thing> for String {
346 fn into(self) -> Thing {
347 Thing {
348 a: self
349 }
350 }
351}
352"#,
353 );
354 }
355}