diff options
author | Graeme Coupar <[email protected]> | 2021-04-02 14:00:56 +0100 |
---|---|---|
committer | Graeme Coupar <[email protected]> | 2021-04-03 15:48:35 +0100 |
commit | ee0384901784b2cbe8d62f259f8598cc0fc7d306 (patch) | |
tree | cd03b30c585b44eedd9e12b90617a4969bae5d81 /crates/ide_assists/src | |
parent | 71ef64b673595807ccb4b3f5b7ad6ea55e63645b (diff) |
Convert Into to From assist
This adds a "Convert Into to From" assist, useful since clippy has
recently started adding lints on every `Into`.
It covers converting the signature, and converting any `self`/`Self`
references within the body to the correct types.
It does assume that every instance of `Into` can be converted to a
`From`, which I _think_ is the case now. Let me know if there's
something I'm not thinking of and I can try and make it smarter.
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/handlers/convert_into_to_from.rs | 355 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 32 |
3 files changed, 389 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/convert_into_to_from.rs b/crates/ide_assists/src/handlers/convert_into_to_from.rs new file mode 100644 index 000000000..199e1ad5c --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_into_to_from.rs | |||
@@ -0,0 +1,355 @@ | |||
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 | } | ||
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 3d1dcef4c..3e2c82dac 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -117,6 +117,7 @@ mod handlers { | |||
117 | mod convert_integer_literal; | 117 | mod convert_integer_literal; |
118 | mod convert_comment_block; | 118 | mod convert_comment_block; |
119 | mod convert_iter_for_each_to_for; | 119 | mod convert_iter_for_each_to_for; |
120 | mod convert_into_to_from; | ||
120 | mod early_return; | 121 | mod early_return; |
121 | mod expand_glob_import; | 122 | mod expand_glob_import; |
122 | mod extract_function; | 123 | mod extract_function; |
@@ -185,6 +186,7 @@ mod handlers { | |||
185 | convert_integer_literal::convert_integer_literal, | 186 | convert_integer_literal::convert_integer_literal, |
186 | convert_comment_block::convert_comment_block, | 187 | convert_comment_block::convert_comment_block, |
187 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, | 188 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, |
189 | convert_into_to_from::convert_into_to_from, | ||
188 | early_return::convert_to_guarded_return, | 190 | early_return::convert_to_guarded_return, |
189 | expand_glob_import::expand_glob_import, | 191 | expand_glob_import::expand_glob_import, |
190 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 192 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 03b7fb366..27a22ca10 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -206,6 +206,38 @@ const _: i32 = 0b1010; | |||
206 | } | 206 | } |
207 | 207 | ||
208 | #[test] | 208 | #[test] |
209 | fn doctest_convert_into_to_from() { | ||
210 | check_doc_test( | ||
211 | "convert_into_to_from", | ||
212 | r#####" | ||
213 | //- /lib.rs crate:core | ||
214 | pub mod convert { pub trait Into<T> { pub fn into(self) -> T; } } | ||
215 | //- /lib.rs crate:main deps:core | ||
216 | use core::convert::Into; | ||
217 | impl $0Into<Thing> for usize { | ||
218 | fn into(self) -> Thing { | ||
219 | Thing { | ||
220 | b: self.to_string(), | ||
221 | a: self | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | "#####, | ||
226 | r#####" | ||
227 | use core::convert::Into; | ||
228 | impl From<usize> for Thing { | ||
229 | fn from(val: usize) -> Self { | ||
230 | Thing { | ||
231 | b: val.to_string(), | ||
232 | a: val | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | "#####, | ||
237 | ) | ||
238 | } | ||
239 | |||
240 | #[test] | ||
209 | fn doctest_convert_iter_for_each_to_for() { | 241 | fn doctest_convert_iter_for_each_to_for() { |
210 | check_doc_test( | 242 | check_doc_test( |
211 | "convert_iter_for_each_to_for", | 243 | "convert_iter_for_each_to_for", |