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