aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-04-04 12:38:20 +0100
committerGitHub <[email protected]>2021-04-04 12:38:20 +0100
commitbc8b27884132a4dbfa019f7d3d5fcbbf9f4912af (patch)
tree7a1a8e677099a17ac548840c49a1d9ff0a898cce /crates/ide_assists
parent082996032054031bd1b68ee45ab04293f4877e91 (diff)
parentee0384901784b2cbe8d62f259f8598cc0fc7d306 (diff)
Merge #8295
8295: Add `convert_into_to_from` assist r=Veykril a=obmarg 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. 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. Closes #8196 ![CleanShot 2021-04-02 at 13 39 54](https://user-images.githubusercontent.com/556490/113420108-9ce21c00-93c0-11eb-8c49-80b5fb189284.gif) I'm extremely new to this codebase so please let me know if anything needs changed. Co-authored-by: Graeme Coupar <[email protected]>
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/handlers/convert_into_to_from.rs355
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs32
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 @@
1use ide_db::{
2 helpers::{mod_path_to_ast, FamousDefs},
3 traits::resolve_target_trait,
4};
5use syntax::ast::{self, AstNode, NameOwner};
6
7use 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// ```
39pub(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)]
114mod 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#"
123struct Thing {
124 a: String,
125 b: usize
126}
127
128impl $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#"
138struct Thing {
139 a: String,
140 b: usize
141}
142
143impl 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#"
159enum Thing {
160 Foo(String),
161 Bar(String)
162}
163
164impl $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#"
174enum Thing {
175 Foo(String),
176 Bar(String)
177}
178
179impl 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#"
195enum Thing<'a> {
196 Foo(&'a str),
197 Bar(&'a str)
198}
199
200impl<'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#"
210enum Thing<'a> {
211 Foo(&'a str),
212 Bar(&'a str)
213}
214
215impl<'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#"
231struct Thing(String);
232
233impl $0core::convert::Into<String> for &Thing {
234 fn into(self) -> Thing {
235 self.0.clone()
236 }
237}
238"#,
239 r#"
240struct Thing(String);
241
242impl 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#"
255mod things {
256 pub struct Thing(String);
257 pub struct BetterThing(String);
258}
259
260impl $0core::convert::Into<things::BetterThing> for &things::Thing {
261 fn into(self) -> Thing {
262 things::BetterThing(self.0.clone())
263 }
264}
265"#,
266 r#"
267mod things {
268 pub struct Thing(String);
269 pub struct BetterThing(String);
270}
271
272impl 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#"
285mod things {
286 pub enum Thing {
287 A(String)
288 }
289 pub struct BetterThing {
290 B(String)
291 }
292}
293
294impl $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#"
303mod things {
304 pub enum Thing {
305 A(String)
306 }
307 pub struct BetterThing {
308 B(String)
309 }
310}
311
312impl 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#"
327pub trait Into<T> {{
328 pub fn into(self) -> T;
329}}
330
331struct Thing {
332 a: String,
333}
334
335impl $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]
209fn doctest_convert_into_to_from() {
210 check_doc_test(
211 "convert_into_to_from",
212 r#####"
213//- /lib.rs crate:core
214pub mod convert { pub trait Into<T> { pub fn into(self) -> T; } }
215//- /lib.rs crate:main deps:core
216use core::convert::Into;
217impl $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#####"
227use core::convert::Into;
228impl 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]
209fn doctest_convert_iter_for_each_to_for() { 241fn 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",