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