]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/convert_into_to_from.rs
Merge #11481
[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(into_fn_params.syntax().text_range(), format!("(val: {})", src_type));
95             builder.replace(into_fn_name.syntax().text_range(), "from");
96
97             for s in selfs {
98                 match s.text().as_ref() {
99                     "self" => builder.replace(s.syntax().text_range(), "val"),
100                     "Self" => builder.replace(s.syntax().text_range(), src_type_path.to_string()),
101                     _ => {}
102                 }
103             }
104         },
105     )
106 }
107
108 #[cfg(test)]
109 mod tests {
110     use super::*;
111
112     use crate::tests::{check_assist, check_assist_not_applicable};
113
114     #[test]
115     fn convert_into_to_from_converts_a_struct() {
116         check_assist(
117             convert_into_to_from,
118             r#"
119 //- minicore: from
120 struct Thing {
121     a: String,
122     b: usize
123 }
124
125 impl $0core::convert::Into<Thing> for usize {
126     fn into(self) -> Thing {
127         Thing {
128             b: self.to_string(),
129             a: self
130         }
131     }
132 }
133 "#,
134             r#"
135 struct Thing {
136     a: String,
137     b: usize
138 }
139
140 impl From<usize> for Thing {
141     fn from(val: usize) -> Self {
142         Thing {
143             b: val.to_string(),
144             a: val
145         }
146     }
147 }
148 "#,
149         )
150     }
151
152     #[test]
153     fn convert_into_to_from_converts_enums() {
154         check_assist(
155             convert_into_to_from,
156             r#"
157 //- minicore: from
158 enum Thing {
159     Foo(String),
160     Bar(String)
161 }
162
163 impl $0core::convert::Into<String> for Thing {
164     fn into(self) -> String {
165         match self {
166             Self::Foo(s) => s,
167             Self::Bar(s) => s
168         }
169     }
170 }
171 "#,
172             r#"
173 enum Thing {
174     Foo(String),
175     Bar(String)
176 }
177
178 impl From<Thing> for String {
179     fn from(val: Thing) -> Self {
180         match val {
181             Thing::Foo(s) => s,
182             Thing::Bar(s) => s
183         }
184     }
185 }
186 "#,
187         )
188     }
189
190     #[test]
191     fn convert_into_to_from_on_enum_with_lifetimes() {
192         check_assist(
193             convert_into_to_from,
194             r#"
195 //- minicore: from
196 enum Thing<'a> {
197     Foo(&'a str),
198     Bar(&'a str)
199 }
200
201 impl<'a> $0core::convert::Into<&'a str> for Thing<'a> {
202     fn into(self) -> &'a str {
203         match self {
204             Self::Foo(s) => s,
205             Self::Bar(s) => s
206         }
207     }
208 }
209 "#,
210             r#"
211 enum Thing<'a> {
212     Foo(&'a str),
213     Bar(&'a str)
214 }
215
216 impl<'a> From<Thing<'a>> for &'a str {
217     fn from(val: Thing<'a>) -> Self {
218         match val {
219             Thing::Foo(s) => s,
220             Thing::Bar(s) => s
221         }
222     }
223 }
224 "#,
225         )
226     }
227
228     #[test]
229     fn convert_into_to_from_works_on_references() {
230         check_assist(
231             convert_into_to_from,
232             r#"
233 //- minicore: from
234 struct Thing(String);
235
236 impl $0core::convert::Into<String> for &Thing {
237     fn into(self) -> Thing {
238         self.0.clone()
239     }
240 }
241 "#,
242             r#"
243 struct Thing(String);
244
245 impl From<&Thing> for String {
246     fn from(val: &Thing) -> Self {
247         val.0.clone()
248     }
249 }
250 "#,
251         )
252     }
253
254     #[test]
255     fn convert_into_to_from_works_on_qualified_structs() {
256         check_assist(
257             convert_into_to_from,
258             r#"
259 //- minicore: from
260 mod things {
261     pub struct Thing(String);
262     pub struct BetterThing(String);
263 }
264
265 impl $0core::convert::Into<things::BetterThing> for &things::Thing {
266     fn into(self) -> Thing {
267         things::BetterThing(self.0.clone())
268     }
269 }
270 "#,
271             r#"
272 mod things {
273     pub struct Thing(String);
274     pub struct BetterThing(String);
275 }
276
277 impl From<&things::Thing> for things::BetterThing {
278     fn from(val: &things::Thing) -> Self {
279         things::BetterThing(val.0.clone())
280     }
281 }
282 "#,
283         )
284     }
285
286     #[test]
287     fn convert_into_to_from_works_on_qualified_enums() {
288         check_assist(
289             convert_into_to_from,
290             r#"
291 //- minicore: from
292 mod things {
293     pub enum Thing {
294         A(String)
295     }
296     pub struct BetterThing {
297         B(String)
298     }
299 }
300
301 impl $0core::convert::Into<things::BetterThing> for &things::Thing {
302     fn into(self) -> Thing {
303         match self {
304             Self::A(s) => things::BetterThing::B(s)
305         }
306     }
307 }
308 "#,
309             r#"
310 mod things {
311     pub enum Thing {
312         A(String)
313     }
314     pub struct BetterThing {
315         B(String)
316     }
317 }
318
319 impl From<&things::Thing> for things::BetterThing {
320     fn from(val: &things::Thing) -> Self {
321         match val {
322             things::Thing::A(s) => things::BetterThing::B(s)
323         }
324     }
325 }
326 "#,
327         )
328     }
329
330     #[test]
331     fn convert_into_to_from_not_applicable_on_any_trait_named_into() {
332         check_assist_not_applicable(
333             convert_into_to_from,
334             r#"
335 //- minicore: from
336 pub trait Into<T> {
337     pub fn into(self) -> T;
338 }
339
340 struct Thing {
341     a: String,
342 }
343
344 impl $0Into<Thing> for String {
345     fn into(self) -> Thing {
346         Thing {
347             a: self
348         }
349     }
350 }
351 "#,
352         );
353     }
354 }