]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs
Rollup merge of #98391 - joboet:sgx_parker, r=m-ou-se
[rust.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / convert_into_to_from.rs
1 use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast, traits::resolve_target_trait};
2 use syntax::ast::{self, AstNode, HasName};
3
4 use crate::{AssistContext, AssistId, AssistKind, 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 // ```
34 pub(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()).core_convert_Into()? {
43         return None;
44     }
45
46     let src_type_path = {
47         let src_type_path = src_type.syntax().descendants().find_map(ast::Path::cast)?;
48         let src_type_def = match ctx.sema.resolve_path(&src_type_path) {
49             Some(hir::PathResolution::Def(module_def)) => module_def,
50             _ => return None,
51         };
52
53         mod_path_to_ast(&module.find_use_path(ctx.db(), src_type_def, ctx.config.prefer_no_std)?)
54     };
55
56     let dest_type = match &ast_trait {
57         ast::Type::PathType(path) => {
58             path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
59         }
60         _ => return None,
61     };
62
63     let into_fn = impl_.assoc_item_list()?.assoc_items().find_map(|item| {
64         if let ast::AssocItem::Fn(f) = item {
65             if f.name()?.text() == "into" {
66                 return Some(f);
67             }
68         };
69         None
70     })?;
71
72     let into_fn_name = into_fn.name()?;
73     let into_fn_params = into_fn.param_list()?;
74     let into_fn_return = into_fn.ret_type()?;
75
76     let selfs = into_fn
77         .body()?
78         .syntax()
79         .descendants()
80         .filter_map(ast::NameRef::cast)
81         .filter(|name| name.text() == "self" || name.text() == "Self");
82
83     acc.add(
84         AssistId("convert_into_to_from", AssistKind::RefactorRewrite),
85         "Convert Into to From",
86         impl_.syntax().text_range(),
87         |builder| {
88             builder.replace(src_type.syntax().text_range(), dest_type.to_string());
89             builder.replace(ast_trait.syntax().text_range(), format!("From<{src_type}>"));
90             builder.replace(into_fn_return.syntax().text_range(), "-> Self");
91             builder.replace(into_fn_params.syntax().text_range(), format!("(val: {src_type})"));
92             builder.replace(into_fn_name.syntax().text_range(), "from");
93
94             for s in selfs {
95                 match s.text().as_ref() {
96                     "self" => builder.replace(s.syntax().text_range(), "val"),
97                     "Self" => builder.replace(s.syntax().text_range(), src_type_path.to_string()),
98                     _ => {}
99                 }
100             }
101         },
102     )
103 }
104
105 #[cfg(test)]
106 mod tests {
107     use super::*;
108
109     use crate::tests::{check_assist, check_assist_not_applicable};
110
111     #[test]
112     fn convert_into_to_from_converts_a_struct() {
113         check_assist(
114             convert_into_to_from,
115             r#"
116 //- minicore: from
117 struct Thing {
118     a: String,
119     b: usize
120 }
121
122 impl $0core::convert::Into<Thing> for usize {
123     fn into(self) -> Thing {
124         Thing {
125             b: self.to_string(),
126             a: self
127         }
128     }
129 }
130 "#,
131             r#"
132 struct Thing {
133     a: String,
134     b: usize
135 }
136
137 impl From<usize> for Thing {
138     fn from(val: usize) -> Self {
139         Thing {
140             b: val.to_string(),
141             a: val
142         }
143     }
144 }
145 "#,
146         )
147     }
148
149     #[test]
150     fn convert_into_to_from_converts_enums() {
151         check_assist(
152             convert_into_to_from,
153             r#"
154 //- minicore: from
155 enum Thing {
156     Foo(String),
157     Bar(String)
158 }
159
160 impl $0core::convert::Into<String> for Thing {
161     fn into(self) -> String {
162         match self {
163             Self::Foo(s) => s,
164             Self::Bar(s) => s
165         }
166     }
167 }
168 "#,
169             r#"
170 enum Thing {
171     Foo(String),
172     Bar(String)
173 }
174
175 impl From<Thing> for String {
176     fn from(val: Thing) -> Self {
177         match val {
178             Thing::Foo(s) => s,
179             Thing::Bar(s) => s
180         }
181     }
182 }
183 "#,
184         )
185     }
186
187     #[test]
188     fn convert_into_to_from_on_enum_with_lifetimes() {
189         check_assist(
190             convert_into_to_from,
191             r#"
192 //- minicore: from
193 enum Thing<'a> {
194     Foo(&'a str),
195     Bar(&'a str)
196 }
197
198 impl<'a> $0core::convert::Into<&'a str> for Thing<'a> {
199     fn into(self) -> &'a str {
200         match self {
201             Self::Foo(s) => s,
202             Self::Bar(s) => s
203         }
204     }
205 }
206 "#,
207             r#"
208 enum Thing<'a> {
209     Foo(&'a str),
210     Bar(&'a str)
211 }
212
213 impl<'a> From<Thing<'a>> for &'a str {
214     fn from(val: Thing<'a>) -> Self {
215         match val {
216             Thing::Foo(s) => s,
217             Thing::Bar(s) => s
218         }
219     }
220 }
221 "#,
222         )
223     }
224
225     #[test]
226     fn convert_into_to_from_works_on_references() {
227         check_assist(
228             convert_into_to_from,
229             r#"
230 //- minicore: from
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_assist(
254             convert_into_to_from,
255             r#"
256 //- minicore: from
257 mod things {
258     pub struct Thing(String);
259     pub struct BetterThing(String);
260 }
261
262 impl $0core::convert::Into<things::BetterThing> for &things::Thing {
263     fn into(self) -> Thing {
264         things::BetterThing(self.0.clone())
265     }
266 }
267 "#,
268             r#"
269 mod things {
270     pub struct Thing(String);
271     pub struct BetterThing(String);
272 }
273
274 impl From<&things::Thing> for things::BetterThing {
275     fn from(val: &things::Thing) -> Self {
276         things::BetterThing(val.0.clone())
277     }
278 }
279 "#,
280         )
281     }
282
283     #[test]
284     fn convert_into_to_from_works_on_qualified_enums() {
285         check_assist(
286             convert_into_to_from,
287             r#"
288 //- minicore: from
289 mod things {
290     pub enum Thing {
291         A(String)
292     }
293     pub struct BetterThing {
294         B(String)
295     }
296 }
297
298 impl $0core::convert::Into<things::BetterThing> for &things::Thing {
299     fn into(self) -> Thing {
300         match self {
301             Self::A(s) => things::BetterThing::B(s)
302         }
303     }
304 }
305 "#,
306             r#"
307 mod things {
308     pub enum Thing {
309         A(String)
310     }
311     pub struct BetterThing {
312         B(String)
313     }
314 }
315
316 impl From<&things::Thing> for things::BetterThing {
317     fn from(val: &things::Thing) -> Self {
318         match val {
319             things::Thing::A(s) => things::BetterThing::B(s)
320         }
321     }
322 }
323 "#,
324         )
325     }
326
327     #[test]
328     fn convert_into_to_from_not_applicable_on_any_trait_named_into() {
329         check_assist_not_applicable(
330             convert_into_to_from,
331             r#"
332 //- minicore: from
333 pub trait Into<T> {
334     pub fn into(self) -> T;
335 }
336
337 struct Thing {
338     a: String,
339 }
340
341 impl $0Into<Thing> for String {
342     fn into(self) -> Thing {
343         Thing {
344             a: self
345         }
346     }
347 }
348 "#,
349         );
350     }
351 }