]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
Merge #9177
[rust.git] / crates / ide_assists / src / handlers / convert_tuple_struct_to_named_struct.rs
1 use either::Either;
2 use ide_db::defs::{Definition, NameRefClass};
3 use syntax::{
4     ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
5     match_ast, SyntaxNode,
6 };
7
8 use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
9
10 // Assist: convert_tuple_struct_to_named_struct
11 //
12 // Converts tuple struct to struct with named fields, and analogously for tuple enum variants.
13 //
14 // ```
15 // struct Point$0(f32, f32);
16 //
17 // impl Point {
18 //     pub fn new(x: f32, y: f32) -> Self {
19 //         Point(x, y)
20 //     }
21 //
22 //     pub fn x(&self) -> f32 {
23 //         self.0
24 //     }
25 //
26 //     pub fn y(&self) -> f32 {
27 //         self.1
28 //     }
29 // }
30 // ```
31 // ->
32 // ```
33 // struct Point { field1: f32, field2: f32 }
34 //
35 // impl Point {
36 //     pub fn new(x: f32, y: f32) -> Self {
37 //         Point { field1: x, field2: y }
38 //     }
39 //
40 //     pub fn x(&self) -> f32 {
41 //         self.field1
42 //     }
43 //
44 //     pub fn y(&self) -> f32 {
45 //         self.field2
46 //     }
47 // }
48 // ```
49 pub(crate) fn convert_tuple_struct_to_named_struct(
50     acc: &mut Assists,
51     ctx: &AssistContext,
52 ) -> Option<()> {
53     let strukt = ctx
54         .find_node_at_offset::<ast::Struct>()
55         .map(Either::Left)
56         .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?;
57     let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?;
58     let tuple_fields = match field_list {
59         ast::FieldList::TupleFieldList(it) => it,
60         ast::FieldList::RecordFieldList(_) => return None,
61     };
62     let strukt_def = match &strukt {
63         Either::Left(s) => Either::Left(ctx.sema.to_def(s)?),
64         Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
65     };
66     let target = strukt.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range();
67
68     acc.add(
69         AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
70         "Convert to named struct",
71         target,
72         |edit| {
73             let names = generate_names(tuple_fields.fields());
74             edit_field_references(ctx, edit, tuple_fields.fields(), &names);
75             edit_struct_references(ctx, edit, strukt_def, &names);
76             edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
77         },
78     )
79 }
80
81 fn edit_struct_def(
82     ctx: &AssistContext,
83     edit: &mut AssistBuilder,
84     strukt: &Either<ast::Struct, ast::Variant>,
85     tuple_fields: ast::TupleFieldList,
86     names: Vec<ast::Name>,
87 ) {
88     let record_fields = tuple_fields
89         .fields()
90         .zip(names)
91         .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?)));
92     let record_fields = ast::make::record_field_list(record_fields);
93     let tuple_fields_text_range = tuple_fields.syntax().text_range();
94
95     edit.edit_file(ctx.frange.file_id);
96
97     if let Either::Left(strukt) = strukt {
98         if let Some(w) = strukt.where_clause() {
99             edit.delete(w.syntax().text_range());
100             edit.insert(
101                 tuple_fields_text_range.start(),
102                 ast::make::tokens::single_newline().text(),
103             );
104             edit.insert(tuple_fields_text_range.start(), w.syntax().text());
105             edit.insert(tuple_fields_text_range.start(), ",");
106             edit.insert(
107                 tuple_fields_text_range.start(),
108                 ast::make::tokens::single_newline().text(),
109             );
110         } else {
111             edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
112         }
113         strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
114     } else {
115         edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
116     }
117
118     edit.replace(tuple_fields_text_range, record_fields.to_string());
119 }
120
121 fn edit_struct_references(
122     ctx: &AssistContext,
123     edit: &mut AssistBuilder,
124     strukt: Either<hir::Struct, hir::Variant>,
125     names: &[ast::Name],
126 ) {
127     let strukt_def = match strukt {
128         Either::Left(s) => Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(s))),
129         Either::Right(v) => Definition::ModuleDef(hir::ModuleDef::Variant(v)),
130     };
131     let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
132
133     let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
134         match_ast! {
135             match node {
136                 ast::TupleStructPat(tuple_struct_pat) => {
137                     edit.replace(
138                         tuple_struct_pat.syntax().text_range(),
139                         ast::make::record_pat_with_fields(
140                             tuple_struct_pat.path()?,
141                             ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
142                                 |(pat, name)| {
143                                     ast::make::record_pat_field(
144                                         ast::make::name_ref(&name.to_string()),
145                                         pat,
146                                     )
147                                 },
148                             )),
149                         )
150                         .to_string(),
151                     );
152                 },
153                 // for tuple struct creations like Foo(42)
154                 ast::CallExpr(call_expr) => {
155                     let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?;
156
157                     // this also includes method calls like Foo::new(42), we should skip them
158                     if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
159                         match NameRefClass::classify(&ctx.sema, &name_ref) {
160                             Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
161                             Some(NameRefClass::Definition(def)) if def == strukt_def => {},
162                             _ => return None,
163                         };
164                     }
165
166                     let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
167
168                     edit.replace(
169                         call_expr.syntax().text_range(),
170                         ast::make::record_expr(
171                             path,
172                             ast::make::record_expr_field_list(arg_list.args().zip(names).map(
173                                 |(expr, name)| {
174                                     ast::make::record_expr_field(
175                                         ast::make::name_ref(&name.to_string()),
176                                         Some(expr),
177                                     )
178                                 },
179                             )),
180                         )
181                         .to_string(),
182                     );
183                 },
184                 _ => return None,
185             }
186         }
187         Some(())
188     };
189
190     for (file_id, refs) in usages {
191         edit.edit_file(file_id);
192         for r in refs {
193             for node in r.name.syntax().ancestors() {
194                 if edit_node(edit, node).is_some() {
195                     break;
196                 }
197             }
198         }
199     }
200 }
201
202 fn edit_field_references(
203     ctx: &AssistContext,
204     edit: &mut AssistBuilder,
205     fields: impl Iterator<Item = ast::TupleField>,
206     names: &[ast::Name],
207 ) {
208     for (field, name) in fields.zip(names) {
209         let field = match ctx.sema.to_def(&field) {
210             Some(it) => it,
211             None => continue,
212         };
213         let def = Definition::Field(field);
214         let usages = def.usages(&ctx.sema).all();
215         for (file_id, refs) in usages {
216             edit.edit_file(file_id);
217             for r in refs {
218                 if let Some(name_ref) = r.name.as_name_ref() {
219                     edit.replace(name_ref.syntax().text_range(), name.text());
220                 }
221             }
222         }
223     }
224 }
225
226 fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
227     fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
228 }
229
230 #[cfg(test)]
231 mod tests {
232     use crate::tests::{check_assist, check_assist_not_applicable};
233
234     use super::*;
235
236     #[test]
237     fn not_applicable_other_than_tuple_struct() {
238         check_assist_not_applicable(
239             convert_tuple_struct_to_named_struct,
240             r#"struct Foo$0 { bar: u32 };"#,
241         );
242         check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
243     }
244
245     #[test]
246     fn convert_simple_struct() {
247         check_assist(
248             convert_tuple_struct_to_named_struct,
249             r#"
250 struct Inner;
251 struct A$0(Inner);
252
253 impl A {
254     fn new(inner: Inner) -> A {
255         A(inner)
256     }
257
258     fn new_with_default() -> A {
259         A::new(Inner)
260     }
261
262     fn into_inner(self) -> Inner {
263         self.0
264     }
265 }"#,
266             r#"
267 struct Inner;
268 struct A { field1: Inner }
269
270 impl A {
271     fn new(inner: Inner) -> A {
272         A { field1: inner }
273     }
274
275     fn new_with_default() -> A {
276         A::new(Inner)
277     }
278
279     fn into_inner(self) -> Inner {
280         self.field1
281     }
282 }"#,
283         );
284     }
285
286     #[test]
287     fn convert_struct_referenced_via_self_kw() {
288         check_assist(
289             convert_tuple_struct_to_named_struct,
290             r#"
291 struct Inner;
292 struct A$0(Inner);
293
294 impl A {
295     fn new(inner: Inner) -> Self {
296         Self(inner)
297     }
298
299     fn new_with_default() -> Self {
300         Self::new(Inner)
301     }
302
303     fn into_inner(self) -> Inner {
304         self.0
305     }
306 }"#,
307             r#"
308 struct Inner;
309 struct A { field1: Inner }
310
311 impl A {
312     fn new(inner: Inner) -> Self {
313         Self { field1: inner }
314     }
315
316     fn new_with_default() -> Self {
317         Self::new(Inner)
318     }
319
320     fn into_inner(self) -> Inner {
321         self.field1
322     }
323 }"#,
324         );
325     }
326
327     #[test]
328     fn convert_destructured_struct() {
329         check_assist(
330             convert_tuple_struct_to_named_struct,
331             r#"
332 struct Inner;
333 struct A$0(Inner);
334
335 impl A {
336     fn into_inner(self) -> Inner {
337         let A(first) = self;
338         first
339     }
340
341     fn into_inner_via_self(self) -> Inner {
342         let Self(first) = self;
343         first
344     }
345 }"#,
346             r#"
347 struct Inner;
348 struct A { field1: Inner }
349
350 impl A {
351     fn into_inner(self) -> Inner {
352         let A { field1: first } = self;
353         first
354     }
355
356     fn into_inner_via_self(self) -> Inner {
357         let Self { field1: first } = self;
358         first
359     }
360 }"#,
361         );
362     }
363
364     #[test]
365     fn convert_struct_with_visibility() {
366         check_assist(
367             convert_tuple_struct_to_named_struct,
368             r#"
369 struct A$0(pub u32, pub(crate) u64);
370
371 impl A {
372     fn new() -> A {
373         A(42, 42)
374     }
375
376     fn into_first(self) -> u32 {
377         self.0
378     }
379
380     fn into_second(self) -> u64 {
381         self.1
382     }
383 }"#,
384             r#"
385 struct A { pub field1: u32, pub(crate) field2: u64 }
386
387 impl A {
388     fn new() -> A {
389         A { field1: 42, field2: 42 }
390     }
391
392     fn into_first(self) -> u32 {
393         self.field1
394     }
395
396     fn into_second(self) -> u64 {
397         self.field2
398     }
399 }"#,
400         );
401     }
402
403     #[test]
404     fn convert_struct_with_wrapped_references() {
405         check_assist(
406             convert_tuple_struct_to_named_struct,
407             r#"
408 struct Inner$0(u32);
409 struct Outer(Inner);
410
411 impl Outer {
412     fn new() -> Self {
413         Self(Inner(42))
414     }
415
416     fn into_inner(self) -> u32 {
417         (self.0).0
418     }
419
420     fn into_inner_destructed(self) -> u32 {
421         let Outer(Inner(x)) = self;
422         x
423     }
424 }"#,
425             r#"
426 struct Inner { field1: u32 }
427 struct Outer(Inner);
428
429 impl Outer {
430     fn new() -> Self {
431         Self(Inner { field1: 42 })
432     }
433
434     fn into_inner(self) -> u32 {
435         (self.0).field1
436     }
437
438     fn into_inner_destructed(self) -> u32 {
439         let Outer(Inner { field1: x }) = self;
440         x
441     }
442 }"#,
443         );
444
445         check_assist(
446             convert_tuple_struct_to_named_struct,
447             r#"
448 struct Inner(u32);
449 struct Outer$0(Inner);
450
451 impl Outer {
452     fn new() -> Self {
453         Self(Inner(42))
454     }
455
456     fn into_inner(self) -> u32 {
457         (self.0).0
458     }
459
460     fn into_inner_destructed(self) -> u32 {
461         let Outer(Inner(x)) = self;
462         x
463     }
464 }"#,
465             r#"
466 struct Inner(u32);
467 struct Outer { field1: Inner }
468
469 impl Outer {
470     fn new() -> Self {
471         Self { field1: Inner(42) }
472     }
473
474     fn into_inner(self) -> u32 {
475         (self.field1).0
476     }
477
478     fn into_inner_destructed(self) -> u32 {
479         let Outer { field1: Inner(x) } = self;
480         x
481     }
482 }"#,
483         );
484     }
485
486     #[test]
487     fn convert_struct_with_multi_file_references() {
488         check_assist(
489             convert_tuple_struct_to_named_struct,
490             r#"
491 //- /main.rs
492 struct Inner;
493 struct A$0(Inner);
494
495 mod foo;
496
497 //- /foo.rs
498 use crate::{A, Inner};
499 fn f() {
500     let a = A(Inner);
501 }
502 "#,
503             r#"
504 //- /main.rs
505 struct Inner;
506 struct A { field1: Inner }
507
508 mod foo;
509
510 //- /foo.rs
511 use crate::{A, Inner};
512 fn f() {
513     let a = A { field1: Inner };
514 }
515 "#,
516         );
517     }
518
519     #[test]
520     fn convert_struct_with_where_clause() {
521         check_assist(
522             convert_tuple_struct_to_named_struct,
523             r#"
524 struct Wrap$0<T>(T)
525 where
526     T: Display;
527 "#,
528             r#"
529 struct Wrap<T>
530 where
531     T: Display,
532 { field1: T }
533
534 "#,
535         );
536     }
537     #[test]
538     fn not_applicable_other_than_tuple_variant() {
539         check_assist_not_applicable(
540             convert_tuple_struct_to_named_struct,
541             r#"enum Enum { Variant$0 { value: usize } };"#,
542         );
543         check_assist_not_applicable(
544             convert_tuple_struct_to_named_struct,
545             r#"enum Enum { Variant$0 }"#,
546         );
547     }
548
549     #[test]
550     fn convert_simple_variant() {
551         check_assist(
552             convert_tuple_struct_to_named_struct,
553             r#"
554 enum A {
555     $0Variant(usize),
556 }
557
558 impl A {
559     fn new(value: usize) -> A {
560         A::Variant(value)
561     }
562
563     fn new_with_default() -> A {
564         A::new(Default::default())
565     }
566
567     fn value(self) -> usize {
568         match self {
569             A::Variant(value) => value,
570         }
571     }
572 }"#,
573             r#"
574 enum A {
575     Variant { field1: usize },
576 }
577
578 impl A {
579     fn new(value: usize) -> A {
580         A::Variant { field1: value }
581     }
582
583     fn new_with_default() -> A {
584         A::new(Default::default())
585     }
586
587     fn value(self) -> usize {
588         match self {
589             A::Variant { field1: value } => value,
590         }
591     }
592 }"#,
593         );
594     }
595
596     #[test]
597     fn convert_variant_referenced_via_self_kw() {
598         check_assist(
599             convert_tuple_struct_to_named_struct,
600             r#"
601 enum A {
602     $0Variant(usize),
603 }
604
605 impl A {
606     fn new(value: usize) -> A {
607         Self::Variant(value)
608     }
609
610     fn new_with_default() -> A {
611         Self::new(Default::default())
612     }
613
614     fn value(self) -> usize {
615         match self {
616             Self::Variant(value) => value,
617         }
618     }
619 }"#,
620             r#"
621 enum A {
622     Variant { field1: usize },
623 }
624
625 impl A {
626     fn new(value: usize) -> A {
627         Self::Variant { field1: value }
628     }
629
630     fn new_with_default() -> A {
631         Self::new(Default::default())
632     }
633
634     fn value(self) -> usize {
635         match self {
636             Self::Variant { field1: value } => value,
637         }
638     }
639 }"#,
640         );
641     }
642
643     #[test]
644     fn convert_destructured_variant() {
645         check_assist(
646             convert_tuple_struct_to_named_struct,
647             r#"
648 enum A {
649     $0Variant(usize),
650 }
651
652 impl A {
653     fn into_inner(self) -> usize {
654         let A::Variant(first) = self;
655         first
656     }
657
658     fn into_inner_via_self(self) -> usize {
659         let Self::Variant(first) = self;
660         first
661     }
662 }"#,
663             r#"
664 enum A {
665     Variant { field1: usize },
666 }
667
668 impl A {
669     fn into_inner(self) -> usize {
670         let A::Variant { field1: first } = self;
671         first
672     }
673
674     fn into_inner_via_self(self) -> usize {
675         let Self::Variant { field1: first } = self;
676         first
677     }
678 }"#,
679         );
680     }
681
682     #[test]
683     fn convert_variant_with_wrapped_references() {
684         check_assist(
685             convert_tuple_struct_to_named_struct,
686             r#"
687 enum Inner {
688     $0Variant(usize),
689 }
690 enum Outer {
691     Variant(Inner),
692 }
693
694 impl Outer {
695     fn new() -> Self {
696         Self::Variant(Inner::Variant(42))
697     }
698
699     fn into_inner_destructed(self) -> u32 {
700         let Outer::Variant(Inner::Variant(x)) = self;
701         x
702     }
703 }"#,
704             r#"
705 enum Inner {
706     Variant { field1: usize },
707 }
708 enum Outer {
709     Variant(Inner),
710 }
711
712 impl Outer {
713     fn new() -> Self {
714         Self::Variant(Inner::Variant { field1: 42 })
715     }
716
717     fn into_inner_destructed(self) -> u32 {
718         let Outer::Variant(Inner::Variant { field1: x }) = self;
719         x
720     }
721 }"#,
722         );
723
724         check_assist(
725             convert_tuple_struct_to_named_struct,
726             r#"
727 enum Inner {
728     Variant(usize),
729 }
730 enum Outer {
731     $0Variant(Inner),
732 }
733
734 impl Outer {
735     fn new() -> Self {
736         Self::Variant(Inner::Variant(42))
737     }
738
739     fn into_inner_destructed(self) -> u32 {
740         let Outer::Variant(Inner::Variant(x)) = self;
741         x
742     }
743 }"#,
744             r#"
745 enum Inner {
746     Variant(usize),
747 }
748 enum Outer {
749     Variant { field1: Inner },
750 }
751
752 impl Outer {
753     fn new() -> Self {
754         Self::Variant { field1: Inner::Variant(42) }
755     }
756
757     fn into_inner_destructed(self) -> u32 {
758         let Outer::Variant { field1: Inner::Variant(x) } = self;
759         x
760     }
761 }"#,
762         );
763     }
764
765     #[test]
766     fn convert_variant_with_multi_file_references() {
767         check_assist(
768             convert_tuple_struct_to_named_struct,
769             r#"
770 //- /main.rs
771 struct Inner;
772 enum A {
773     $0Variant(Inner),
774 }
775
776 mod foo;
777
778 //- /foo.rs
779 use crate::{A, Inner};
780 fn f() {
781     let a = A::Variant(Inner);
782 }
783 "#,
784             r#"
785 //- /main.rs
786 struct Inner;
787 enum A {
788     Variant { field1: Inner },
789 }
790
791 mod foo;
792
793 //- /foo.rs
794 use crate::{A, Inner};
795 fn f() {
796     let a = A::Variant { field1: Inner };
797 }
798 "#,
799         );
800     }
801
802     #[test]
803     fn convert_directly_used_variant() {
804         check_assist(
805             convert_tuple_struct_to_named_struct,
806             r#"
807 //- /main.rs
808 struct Inner;
809 enum A {
810     $0Variant(Inner),
811 }
812
813 mod foo;
814
815 //- /foo.rs
816 use crate::{A::Variant, Inner};
817 fn f() {
818     let a = Variant(Inner);
819 }
820 "#,
821             r#"
822 //- /main.rs
823 struct Inner;
824 enum A {
825     Variant { field1: Inner },
826 }
827
828 mod foo;
829
830 //- /foo.rs
831 use crate::{A::Variant, Inner};
832 fn f() {
833     let a = Variant { field1: Inner };
834 }
835 "#,
836         );
837     }
838 }