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