Skip to main content

ide_assists/handlers/
convert_into_to_from.rs

1use ide_db::{
2    famous_defs::FamousDefs, helpers::mod_path_to_ast_with_factory, traits::resolve_target_trait,
3};
4use syntax::ast::{self, AstNode, HasGenericArgs, HasName};
5
6use crate::{AssistContext, AssistId, Assists};
7
8// FIXME: this should be a diagnostic
9
10// Assist: convert_into_to_from
11//
12// Converts an Into impl to an equivalent From impl.
13//
14// ```
15// # //- minicore: from
16// impl $0Into<Thing> for usize {
17//     fn into(self) -> Thing {
18//         Thing {
19//             b: self.to_string(),
20//             a: self
21//         }
22//     }
23// }
24// ```
25// ->
26// ```
27// impl From<usize> for Thing {
28//     fn from(val: usize) -> Self {
29//         Thing {
30//             b: val.to_string(),
31//             a: val
32//         }
33//     }
34// }
35// ```
36pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
37    let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
38    let src_type = impl_.self_ty()?;
39    let ast_trait = impl_.trait_()?;
40
41    let module = ctx.sema.scope(impl_.syntax())?.module();
42
43    let trait_ = resolve_target_trait(&ctx.sema, &impl_)?;
44    if trait_ != FamousDefs(&ctx.sema, module.krate(ctx.db())).core_convert_Into()? {
45        return None;
46    }
47
48    let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
49    let current_edition = module.krate(ctx.db()).edition(ctx.db());
50
51    let src_type_mod_path = {
52        let src_type_path = src_type.syntax().descendants().find_map(ast::Path::cast)?;
53        let src_type_def = match ctx.sema.resolve_path(&src_type_path) {
54            Some(hir::PathResolution::Def(module_def)) => module_def,
55            _ => return None,
56        };
57        module.find_path(ctx.db(), src_type_def, cfg)?
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            let editor = builder.make_editor(impl_.syntax());
93            let make = editor.make();
94            let src_type_path =
95                mod_path_to_ast_with_factory(make, &src_type_mod_path, current_edition);
96
97            editor.replace(src_type.syntax(), make.ty(&dest_type.to_string()).syntax());
98            editor.replace(ast_trait.syntax(), make.ty(&format!("From<{src_type}>")).syntax());
99            editor.replace(into_fn_return.syntax(), make.ret_type(make.ty("Self")).syntax());
100
101            editor.replace(
102                into_fn_params.syntax(),
103                make.param_list(
104                    None,
105                    [make.param(make.simple_ident_pat(make.name("val")).into(), src_type.clone())],
106                )
107                .syntax(),
108            );
109            editor.replace(into_fn_name.syntax(), make.name("from").syntax());
110
111            for s in selfs {
112                match s.text().as_ref() {
113                    "self" => editor.replace(s.syntax(), make.name_ref("val").syntax()),
114                    "Self" => {
115                        if let Some(path_segment) =
116                            s.syntax().parent().and_then(ast::PathSegment::cast)
117                        {
118                            let self_path = path_segment.parent_path();
119                            if self_path.qualifier().is_none()
120                                && path_segment.generic_arg_list().is_none()
121                            {
122                                editor.replace(self_path.syntax(), src_type_path.syntax());
123                            }
124                        }
125                    }
126                    _ => {}
127                }
128            }
129
130            builder.add_file_edits(ctx.vfs_file_id(), editor);
131        },
132    )
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    use crate::tests::{check_assist, check_assist_not_applicable};
140
141    #[test]
142    fn convert_into_to_from_converts_a_struct() {
143        check_assist(
144            convert_into_to_from,
145            r#"
146//- minicore: from
147struct Thing {
148    a: String,
149    b: usize
150}
151
152impl $0core::convert::Into<Thing> for usize {
153    fn into(self) -> Thing {
154        Thing {
155            b: self.to_string(),
156            a: self
157        }
158    }
159}
160"#,
161            r#"
162struct Thing {
163    a: String,
164    b: usize
165}
166
167impl From<usize> for Thing {
168    fn from(val: usize) -> Self {
169        Thing {
170            b: val.to_string(),
171            a: val
172        }
173    }
174}
175"#,
176        )
177    }
178
179    #[test]
180    fn convert_into_to_from_converts_enums() {
181        check_assist(
182            convert_into_to_from,
183            r#"
184//- minicore: from
185enum Thing {
186    Foo(String),
187    Bar(String)
188}
189
190impl $0core::convert::Into<String> for Thing {
191    fn into(self) -> String {
192        match self {
193            Self::Foo(s) => s,
194            Self::Bar(s) => s
195        }
196    }
197}
198"#,
199            r#"
200enum Thing {
201    Foo(String),
202    Bar(String)
203}
204
205impl From<Thing> for String {
206    fn from(val: Thing) -> Self {
207        match val {
208            Thing::Foo(s) => s,
209            Thing::Bar(s) => s
210        }
211    }
212}
213"#,
214        )
215    }
216
217    #[test]
218    fn convert_into_to_from_on_enum_with_lifetimes() {
219        check_assist(
220            convert_into_to_from,
221            r#"
222//- minicore: from
223enum Thing<'a> {
224    Foo(&'a str),
225    Bar(&'a str)
226}
227
228impl<'a> $0core::convert::Into<&'a str> for Thing<'a> {
229    fn into(self) -> &'a str {
230        match self {
231            Self::Foo(s) => s,
232            Self::Bar(s) => s
233        }
234    }
235}
236"#,
237            r#"
238enum Thing<'a> {
239    Foo(&'a str),
240    Bar(&'a str)
241}
242
243impl<'a> From<Thing<'a>> for &'a str {
244    fn from(val: Thing<'a>) -> Self {
245        match val {
246            Thing::Foo(s) => s,
247            Thing::Bar(s) => s
248        }
249    }
250}
251"#,
252        )
253    }
254
255    #[test]
256    fn convert_into_to_from_works_on_references() {
257        check_assist(
258            convert_into_to_from,
259            r#"
260//- minicore: from
261struct Thing(String);
262
263impl $0core::convert::Into<String> for &Thing {
264    fn into(self) -> Thing {
265        self.0.clone()
266    }
267}
268"#,
269            r#"
270struct Thing(String);
271
272impl From<&Thing> for String {
273    fn from(val: &Thing) -> Self {
274        val.0.clone()
275    }
276}
277"#,
278        )
279    }
280
281    #[test]
282    fn convert_into_to_from_works_on_qualified_structs() {
283        check_assist(
284            convert_into_to_from,
285            r#"
286//- minicore: from
287mod things {
288    pub struct Thing(String);
289    pub struct BetterThing(String);
290}
291
292impl $0core::convert::Into<things::BetterThing> for &things::Thing {
293    fn into(self) -> Thing {
294        things::BetterThing(self.0.clone())
295    }
296}
297"#,
298            r#"
299mod things {
300    pub struct Thing(String);
301    pub struct BetterThing(String);
302}
303
304impl From<&things::Thing> for things::BetterThing {
305    fn from(val: &things::Thing) -> Self {
306        things::BetterThing(val.0.clone())
307    }
308}
309"#,
310        )
311    }
312
313    #[test]
314    fn convert_into_to_from_works_on_qualified_enums() {
315        check_assist(
316            convert_into_to_from,
317            r#"
318//- minicore: from
319mod things {
320    pub enum Thing {
321        A(String)
322    }
323    pub struct BetterThing {
324        B(String)
325    }
326}
327
328impl $0core::convert::Into<things::BetterThing> for &things::Thing {
329    fn into(self) -> Thing {
330        match self {
331            Self::A(s) => things::BetterThing::B(s)
332        }
333    }
334}
335"#,
336            r#"
337mod things {
338    pub enum Thing {
339        A(String)
340    }
341    pub struct BetterThing {
342        B(String)
343    }
344}
345
346impl From<&things::Thing> for things::BetterThing {
347    fn from(val: &things::Thing) -> Self {
348        match val {
349            things::Thing::A(s) => things::BetterThing::B(s)
350        }
351    }
352}
353"#,
354        )
355    }
356
357    #[test]
358    fn convert_into_to_from_not_applicable_on_any_trait_named_into() {
359        check_assist_not_applicable(
360            convert_into_to_from,
361            r#"
362//- minicore: from
363pub trait Into<T> {
364    pub fn into(self) -> T;
365}
366
367struct Thing {
368    a: String,
369}
370
371impl $0Into<Thing> for String {
372    fn into(self) -> Thing {
373        Thing {
374            a: self
375        }
376    }
377}
378"#,
379        );
380    }
381}