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