ide_assists/handlers/
convert_into_to_from.rs

1use 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
6// FIXME: this should be a diagnostic
7
8// Assist: convert_into_to_from
9//
10// Converts an Into impl to an equivalent From impl.
11//
12// ```
13// # //- minicore: from
14// impl $0Into<Thing> for usize {
15//     fn into(self) -> Thing {
16//         Thing {
17//             b: self.to_string(),
18//             a: self
19//         }
20//     }
21// }
22// ```
23// ->
24// ```
25// impl From<usize> for Thing {
26//     fn from(val: usize) -> Self {
27//         Thing {
28//             b: val.to_string(),
29//             a: val
30//         }
31//     }
32// }
33// ```
34pub(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}