]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
Add missing test case for "Convert to named struct" assist
[rust.git] / crates / ide_assists / src / handlers / convert_tuple_struct_to_named_struct.rs
1 use ide_db::defs::{Definition, NameRefClass};
2 use syntax::{
3     ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
4     match_ast, SyntaxNode,
5 };
6
7 use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
8
9 // Assist: convert_tuple_struct_to_named_struct
10 //
11 // Converts tuple struct to struct with named fields.
12 //
13 // ```
14 // struct Point$0(f32, f32);
15 //
16 // impl Point {
17 //     pub fn new(x: f32, y: f32) -> Self {
18 //         Point(x, y)
19 //     }
20 //
21 //     pub fn x(&self) -> f32 {
22 //         self.0
23 //     }
24 //
25 //     pub fn y(&self) -> f32 {
26 //         self.1
27 //     }
28 // }
29 // ```
30 // ->
31 // ```
32 // struct Point { field1: f32, field2: f32 }
33 //
34 // impl Point {
35 //     pub fn new(x: f32, y: f32) -> Self {
36 //         Point { field1: x, field2: y }
37 //     }
38 //
39 //     pub fn x(&self) -> f32 {
40 //         self.field1
41 //     }
42 //
43 //     pub fn y(&self) -> f32 {
44 //         self.field2
45 //     }
46 // }
47 // ```
48 pub(crate) fn convert_tuple_struct_to_named_struct(
49     acc: &mut Assists,
50     ctx: &AssistContext,
51 ) -> Option<()> {
52     let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
53     let tuple_fields = match strukt.field_list()? {
54         ast::FieldList::TupleFieldList(it) => it,
55         ast::FieldList::RecordFieldList(_) => return None,
56     };
57     let strukt_def = ctx.sema.to_def(&strukt)?;
58
59     let target = strukt.syntax().text_range();
60     acc.add(
61         AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
62         "Convert to named struct",
63         target,
64         |edit| {
65             let names = generate_names(tuple_fields.fields());
66             edit_field_references(ctx, edit, tuple_fields.fields(), &names);
67             edit_struct_references(ctx, edit, strukt_def, &names);
68             edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
69         },
70     )
71 }
72
73 fn edit_struct_def(
74     ctx: &AssistContext,
75     edit: &mut AssistBuilder,
76     strukt: &ast::Struct,
77     tuple_fields: ast::TupleFieldList,
78     names: Vec<ast::Name>,
79 ) {
80     let record_fields = tuple_fields
81         .fields()
82         .zip(names)
83         .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?)));
84     let record_fields = ast::make::record_field_list(record_fields);
85     let tuple_fields_text_range = tuple_fields.syntax().text_range();
86
87     edit.edit_file(ctx.frange.file_id);
88
89     if let Some(w) = strukt.where_clause() {
90         edit.delete(w.syntax().text_range());
91         edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
92         edit.insert(tuple_fields_text_range.start(), w.syntax().text());
93         edit.insert(tuple_fields_text_range.start(), ",");
94         edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
95     } else {
96         edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
97     }
98
99     edit.replace(tuple_fields_text_range, record_fields.to_string());
100     strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
101 }
102
103 fn edit_struct_references(
104     ctx: &AssistContext,
105     edit: &mut AssistBuilder,
106     strukt: hir::Struct,
107     names: &[ast::Name],
108 ) {
109     let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt)));
110     let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all();
111
112     let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
113         match_ast! {
114             match node {
115                 ast::TupleStructPat(tuple_struct_pat) => {
116                     edit.replace(
117                         tuple_struct_pat.syntax().text_range(),
118                         ast::make::record_pat_with_fields(
119                             tuple_struct_pat.path()?,
120                             ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
121                                 |(pat, name)| {
122                                     ast::make::record_pat_field(
123                                         ast::make::name_ref(&name.to_string()),
124                                         pat,
125                                     )
126                                 },
127                             )),
128                         )
129                         .to_string(),
130                     );
131                 },
132                 // for tuple struct creations like Foo(42)
133                 ast::CallExpr(call_expr) => {
134                     let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?;
135
136                     // this also includes method calls like Foo::new(42), we should skip them
137                     if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
138                         match NameRefClass::classify(&ctx.sema, &name_ref) {
139                             Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
140                             Some(NameRefClass::Definition(def)) if def == strukt_def => {},
141                             _ => return None,
142                         };
143                     }
144
145                     let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
146
147                     edit.replace(
148                         call_expr.syntax().text_range(),
149                         ast::make::record_expr(
150                             path,
151                             ast::make::record_expr_field_list(arg_list.args().zip(names).map(
152                                 |(expr, name)| {
153                                     ast::make::record_expr_field(
154                                         ast::make::name_ref(&name.to_string()),
155                                         Some(expr),
156                                     )
157                                 },
158                             )),
159                         )
160                         .to_string(),
161                     );
162                 },
163                 _ => return None,
164             }
165         }
166         Some(())
167     };
168
169     for (file_id, refs) in usages {
170         edit.edit_file(file_id);
171         for r in refs {
172             for node in r.name.syntax().ancestors() {
173                 if edit_node(edit, node).is_some() {
174                     break;
175                 }
176             }
177         }
178     }
179 }
180
181 fn edit_field_references(
182     ctx: &AssistContext,
183     edit: &mut AssistBuilder,
184     fields: impl Iterator<Item = ast::TupleField>,
185     names: &[ast::Name],
186 ) {
187     for (field, name) in fields.zip(names) {
188         let field = match ctx.sema.to_def(&field) {
189             Some(it) => it,
190             None => continue,
191         };
192         let def = Definition::Field(field);
193         let usages = def.usages(&ctx.sema).all();
194         for (file_id, refs) in usages {
195             edit.edit_file(file_id);
196             for r in refs {
197                 if let Some(name_ref) = r.name.as_name_ref() {
198                     edit.replace(name_ref.syntax().text_range(), name.text());
199                 }
200             }
201         }
202     }
203 }
204
205 fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
206     fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
207 }
208
209 #[cfg(test)]
210 mod tests {
211     use crate::tests::{check_assist, check_assist_not_applicable};
212
213     use super::*;
214
215     #[test]
216     fn not_applicable_other_than_tuple_struct() {
217         check_assist_not_applicable(
218             convert_tuple_struct_to_named_struct,
219             r#"struct Foo$0 { bar: u32 };"#,
220         );
221         check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
222     }
223
224     #[test]
225     fn convert_simple_struct() {
226         check_assist(
227             convert_tuple_struct_to_named_struct,
228             r#"
229 struct Inner;
230 struct A$0(Inner);
231
232 impl A {
233     fn new(inner: Inner) -> A {
234         A(inner)
235     }
236
237     fn new_with_default() -> A {
238         A::new(Inner)
239     }
240
241     fn into_inner(self) -> Inner {
242         self.0
243     }
244 }"#,
245             r#"
246 struct Inner;
247 struct A { field1: Inner }
248
249 impl A {
250     fn new(inner: Inner) -> A {
251         A { field1: inner }
252     }
253
254     fn new_with_default() -> A {
255         A::new(Inner)
256     }
257
258     fn into_inner(self) -> Inner {
259         self.field1
260     }
261 }"#,
262         );
263     }
264
265     #[test]
266     fn convert_struct_referenced_via_self_kw() {
267         check_assist(
268             convert_tuple_struct_to_named_struct,
269             r#"
270 struct Inner;
271 struct A$0(Inner);
272
273 impl A {
274     fn new(inner: Inner) -> Self {
275         Self(inner)
276     }
277
278     fn new_with_default() -> Self {
279         Self::new(Inner)
280     }
281
282     fn into_inner(self) -> Inner {
283         self.0
284     }
285 }"#,
286             r#"
287 struct Inner;
288 struct A { field1: Inner }
289
290 impl A {
291     fn new(inner: Inner) -> Self {
292         Self { field1: inner }
293     }
294
295     fn new_with_default() -> Self {
296         Self::new(Inner)
297     }
298
299     fn into_inner(self) -> Inner {
300         self.field1
301     }
302 }"#,
303         );
304     }
305
306     #[test]
307     fn convert_destructured_struct() {
308         check_assist(
309             convert_tuple_struct_to_named_struct,
310             r#"
311 struct Inner;
312 struct A$0(Inner);
313
314 impl A {
315     fn into_inner(self) -> Inner {
316         let A(first) = self;
317         first
318     }
319
320     fn into_inner_via_self(self) -> Inner {
321         let Self(first) = self;
322         first
323     }
324 }"#,
325             r#"
326 struct Inner;
327 struct A { field1: Inner }
328
329 impl A {
330     fn into_inner(self) -> Inner {
331         let A { field1: first } = self;
332         first
333     }
334
335     fn into_inner_via_self(self) -> Inner {
336         let Self { field1: first } = self;
337         first
338     }
339 }"#,
340         );
341     }
342
343     #[test]
344     fn convert_struct_with_visibility() {
345         check_assist(
346             convert_tuple_struct_to_named_struct,
347             r#"
348 struct A$0(pub u32, pub(crate) u64);
349
350 impl A {
351     fn new() -> A {
352         A(42, 42)
353     }
354
355     fn into_first(self) -> u32 {
356         self.0
357     }
358
359     fn into_second(self) -> u64 {
360         self.1
361     }
362 }"#,
363             r#"
364 struct A { pub field1: u32, pub(crate) field2: u64 }
365
366 impl A {
367     fn new() -> A {
368         A { field1: 42, field2: 42 }
369     }
370
371     fn into_first(self) -> u32 {
372         self.field1
373     }
374
375     fn into_second(self) -> u64 {
376         self.field2
377     }
378 }"#,
379         );
380     }
381
382     #[test]
383     fn convert_struct_with_wrapped_references() {
384         check_assist(
385             convert_tuple_struct_to_named_struct,
386             r#"
387 struct Inner$0(u32);
388 struct Outer(Inner);
389
390 impl Outer {
391     fn new() -> Self {
392         Self(Inner(42))
393     }
394
395     fn into_inner(self) -> u32 {
396         (self.0).0
397     }
398
399     fn into_inner_destructed(self) -> u32 {
400         let Outer(Inner(x)) = self;
401         x
402     }
403 }"#,
404             r#"
405 struct Inner { field1: u32 }
406 struct Outer(Inner);
407
408 impl Outer {
409     fn new() -> Self {
410         Self(Inner { field1: 42 })
411     }
412
413     fn into_inner(self) -> u32 {
414         (self.0).field1
415     }
416
417     fn into_inner_destructed(self) -> u32 {
418         let Outer(Inner { field1: x }) = self;
419         x
420     }
421 }"#,
422         );
423
424         check_assist(
425             convert_tuple_struct_to_named_struct,
426             r#"
427 struct Inner(u32);
428 struct Outer$0(Inner);
429
430 impl Outer {
431     fn new() -> Self {
432         Self(Inner(42))
433     }
434
435     fn into_inner(self) -> u32 {
436         (self.0).0
437     }
438
439     fn into_inner_destructed(self) -> u32 {
440         let Outer(Inner(x)) = self;
441         x
442     }
443 }"#,
444             r#"
445 struct Inner(u32);
446 struct Outer { field1: Inner }
447
448 impl Outer {
449     fn new() -> Self {
450         Self { field1: Inner(42) }
451     }
452
453     fn into_inner(self) -> u32 {
454         (self.field1).0
455     }
456
457     fn into_inner_destructed(self) -> u32 {
458         let Outer { field1: Inner(x) } = self;
459         x
460     }
461 }"#,
462         );
463     }
464
465     #[test]
466     fn convert_struct_with_multi_file_references() {
467         check_assist(
468             convert_tuple_struct_to_named_struct,
469             r#"
470 //- /main.rs
471 struct Inner;
472 struct A$0(Inner);
473
474 mod foo;
475
476 //- /foo.rs
477 use crate::{A, Inner};
478 fn f() {
479     let a = A(Inner);
480 }
481 "#,
482             r#"
483 //- /main.rs
484 struct Inner;
485 struct A { field1: Inner }
486
487 mod foo;
488
489 //- /foo.rs
490 use crate::{A, Inner};
491 fn f() {
492     let a = A { field1: Inner };
493 }
494 "#,
495         );
496     }
497
498     #[test]
499     fn convert_struct_with_where_clause() {
500         check_assist(
501             convert_tuple_struct_to_named_struct,
502             r#"
503 struct Wrap$0<T>(T)
504 where
505     T: Display;
506 "#,
507             r#"
508 struct Wrap<T>
509 where
510     T: Display,
511 { field1: T }
512
513 "#,
514         );
515     }
516 }