Skip to main content

ide_assists/handlers/
convert_named_struct_to_tuple_struct.rs

1use either::Either;
2use ide_db::{defs::Definition, search::FileReference};
3use syntax::{
4    NodeOrToken, SyntaxKind, SyntaxNode, T,
5    algo::next_non_trivia_token,
6    ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility},
7    match_ast,
8    syntax_editor::{Element, Position, SyntaxEditor},
9};
10
11use crate::{
12    AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder, utils::cover_edit_range,
13};
14
15// Assist: convert_named_struct_to_tuple_struct
16//
17// Converts struct with named fields to tuple struct, and analogously for enum variants with named
18// fields.
19//
20// ```
21// struct Point$0 { x: f32, y: f32 }
22//
23// impl Point {
24//     pub fn new(x: f32, y: f32) -> Self {
25//         Point { x, y }
26//     }
27//
28//     pub fn x(&self) -> f32 {
29//         self.x
30//     }
31//
32//     pub fn y(&self) -> f32 {
33//         self.y
34//     }
35// }
36// ```
37// ->
38// ```
39// struct Point(f32, f32);
40//
41// impl Point {
42//     pub fn new(x: f32, y: f32) -> Self {
43//         Point(x, y)
44//     }
45//
46//     pub fn x(&self) -> f32 {
47//         self.0
48//     }
49//
50//     pub fn y(&self) -> f32 {
51//         self.1
52//     }
53// }
54// ```
55pub(crate) fn convert_named_struct_to_tuple_struct(
56    acc: &mut Assists,
57    ctx: &AssistContext<'_, '_>,
58) -> Option<()> {
59    // XXX: We don't currently provide this assist for struct definitions inside macros, but if we
60    // are to lift this limitation, don't forget to make `edit_struct_def()` consider macro files
61    // too.
62    let strukt_or_variant = ctx
63        .find_node_at_offset::<ast::Struct>()
64        .map(Either::Left)
65        .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?;
66    let field_list = strukt_or_variant.as_ref().either(|s| s.field_list(), |v| v.field_list())?;
67
68    if ctx.offset() > field_list.syntax().text_range().start() {
69        // Assist could be distracting after the braces
70        return None;
71    }
72
73    let record_fields = match field_list {
74        ast::FieldList::RecordFieldList(it) => it,
75        ast::FieldList::TupleFieldList(_) => return None,
76    };
77    let strukt_def = match &strukt_or_variant {
78        Either::Left(s) => Either::Left(ctx.sema.to_def(s)?),
79        Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
80    };
81
82    acc.add(
83        AssistId::refactor_rewrite("convert_named_struct_to_tuple_struct"),
84        "Convert to tuple struct",
85        strukt_or_variant.syntax().text_range(),
86        |builder| {
87            edit_field_references(ctx, builder, record_fields.fields());
88            edit_struct_references(ctx, builder, strukt_def);
89            edit_struct_def(ctx, builder, &strukt_or_variant, record_fields);
90        },
91    )
92}
93
94fn edit_struct_def(
95    ctx: &AssistContext<'_, '_>,
96    builder: &mut SourceChangeBuilder,
97    strukt: &Either<ast::Struct, ast::Variant>,
98    record_fields: ast::RecordFieldList,
99) {
100    // Note that we don't need to consider macro files in this function because this is
101    // currently not triggered for struct definitions inside macro calls.
102    let editor = builder.make_editor(strukt.syntax());
103    let make = editor.make();
104
105    let tuple_fields = record_fields.fields().filter_map(|f| {
106        let (field_editor, field) =
107            SyntaxEditor::with_ast_node(&make.tuple_field(f.visibility(), f.ty()?));
108        field_editor.insert_all(
109            Position::first_child_of(field.syntax()),
110            f.attrs().map(|attr| attr.syntax().clone().into()).collect(),
111        );
112        let field_syntax = field_editor.finish().new_root().clone();
113        ast::TupleField::cast(field_syntax)
114    });
115
116    let tuple_fields = make.tuple_field_list(tuple_fields);
117
118    let mut elements = vec![tuple_fields.syntax().clone().into()];
119    if let Either::Left(strukt) = strukt {
120        if let Some(w) = strukt.where_clause() {
121            editor.delete(w.syntax());
122
123            elements.extend([
124                make.whitespace("\n").into(),
125                remove_trailing_comma(w).into(),
126                make.token(T![;]).into(),
127                make.whitespace("\n").into(),
128            ]);
129
130            if let Some(tok) = strukt
131                .generic_param_list()
132                .and_then(|l| l.r_angle_token())
133                .and_then(|tok| tok.next_token())
134                .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE)
135            {
136                editor.delete(tok);
137            }
138        } else {
139            elements.push(make.token(T![;]).into());
140        }
141    }
142    editor.replace_with_many(record_fields.syntax(), elements);
143
144    if let Some(tok) = record_fields
145        .l_curly_token()
146        .and_then(|tok| tok.prev_token())
147        .filter(|tok| tok.kind() == SyntaxKind::WHITESPACE)
148    {
149        editor.delete(tok)
150    }
151
152    builder.add_file_edits(ctx.vfs_file_id(), editor);
153}
154
155fn edit_struct_references(
156    ctx: &AssistContext<'_, '_>,
157    builder: &mut SourceChangeBuilder,
158    strukt: Either<hir::Struct, hir::EnumVariant>,
159) {
160    let strukt_def = match strukt {
161        Either::Left(s) => Definition::Adt(hir::Adt::Struct(s)),
162        Either::Right(v) => Definition::EnumVariant(v),
163    };
164    let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
165
166    for (file_id, refs) in usages {
167        let source = ctx.sema.parse(file_id);
168        let editor = builder.make_editor(source.syntax());
169        for r in refs {
170            process_struct_name_reference(ctx, r, &editor, &source);
171        }
172        builder.add_file_edits(file_id.file_id(ctx.db()), editor);
173    }
174}
175
176fn process_struct_name_reference(
177    ctx: &AssistContext<'_, '_>,
178    r: FileReference,
179    edit: &SyntaxEditor,
180    source: &ast::SourceFile,
181) -> Option<()> {
182    // First check if it's the last semgnet of a path that directly belongs to a record
183    // expression/pattern.
184    let name_ref = r.name.as_name_ref()?;
185    let path_segment = name_ref.syntax().parent().and_then(ast::PathSegment::cast)?;
186    // A `PathSegment` always belongs to a `Path`, so there's at least one `Path` at this point.
187    let full_path =
188        path_segment.syntax().parent()?.ancestors().map_while(ast::Path::cast).last()?;
189
190    if full_path.segment()?.name_ref()? != *name_ref {
191        // `name_ref` isn't the last segment of the path, so `full_path` doesn't point to the
192        // struct we want to edit.
193        return None;
194    }
195
196    // FIXME: Processing RecordPat and RecordExpr for unordered fields, and insert RestPat
197    let parent = full_path.syntax().parent()?;
198    match_ast! {
199        match parent {
200            ast::RecordPat(record_struct_pat) => {
201                // When we failed to get the original range for the whole struct pattern node,
202                // we can't provide any reasonable edit. Leave it untouched.
203                record_to_tuple_struct_like(
204                    ctx,
205                    source,
206                    edit,
207                    record_struct_pat.record_pat_field_list()?,
208                    |it| it.fields().filter_map(|it| it.name_ref()),
209                );
210            },
211            ast::RecordExpr(record_expr) => {
212                // When we failed to get the original range for the whole struct expression node,
213                // we can't provide any reasonable edit. Leave it untouched.
214                record_to_tuple_struct_like(
215                    ctx,
216                    source,
217                    edit,
218                    record_expr.record_expr_field_list()?,
219                    |it| it.fields().filter_map(|it| it.name_ref()),
220                );
221            },
222            _ => {}
223        }
224    }
225
226    Some(())
227}
228
229fn record_to_tuple_struct_like<T, I>(
230    ctx: &AssistContext<'_, '_>,
231    source: &ast::SourceFile,
232    editor: &SyntaxEditor,
233    field_list: T,
234    fields: impl FnOnce(&T) -> I,
235) -> Option<()>
236where
237    T: AstNode,
238    I: IntoIterator<Item = ast::NameRef>,
239{
240    let make = editor.make();
241    let orig = ctx.sema.original_range_opt(field_list.syntax())?;
242    let list_range = cover_edit_range(source.syntax(), orig.range);
243
244    let l_curly = match list_range.start() {
245        NodeOrToken::Node(node) => node.first_token()?,
246        NodeOrToken::Token(t) => t.clone(),
247    };
248    let r_curly = match list_range.end() {
249        NodeOrToken::Node(node) => node.last_token()?,
250        NodeOrToken::Token(t) => t.clone(),
251    };
252
253    if l_curly.kind() == T!['{'] {
254        delete_whitespace(editor, l_curly.prev_token());
255        delete_whitespace(editor, l_curly.next_token());
256        editor.replace(l_curly, make.token(T!['(']));
257    }
258    if r_curly.kind() == T!['}'] {
259        delete_whitespace(editor, r_curly.prev_token());
260        editor.replace(r_curly, make.token(T![')']));
261    }
262
263    for name_ref in fields(&field_list) {
264        let Some(orig) = ctx.sema.original_range_opt(name_ref.syntax()) else { continue };
265        let name_range = cover_edit_range(source.syntax(), orig.range);
266
267        if let Some(colon) = next_non_trivia_token(name_range.end().clone())
268            && colon.kind() == T![:]
269        {
270            editor.delete(&colon);
271            editor.delete_all(name_range);
272
273            if let Some(next) = next_non_trivia_token(colon.clone())
274                && next.kind() != T!['}']
275            {
276                // Avoid overlapping delete whitespace on `{ field: }`
277                delete_whitespace(editor, colon.next_token());
278            }
279        }
280    }
281    Some(())
282}
283
284fn edit_field_references(
285    ctx: &AssistContext<'_, '_>,
286    builder: &mut SourceChangeBuilder,
287    fields: impl Iterator<Item = ast::RecordField>,
288) {
289    for (index, field) in fields.enumerate() {
290        let field = match ctx.sema.to_def(&field) {
291            Some(it) => it,
292            None => continue,
293        };
294        let def = Definition::Field(field);
295        let usages = def.usages(&ctx.sema).all();
296        for (file_id, refs) in usages {
297            let source = ctx.sema.parse(file_id);
298            let editor = builder.make_editor(source.syntax());
299            let make = editor.make();
300
301            for r in refs {
302                if let Some(name_ref) = r.name.as_name_ref() {
303                    // Only edit the field reference if it's part of a `.field` access
304                    if name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() {
305                        editor.replace_all(
306                            cover_edit_range(source.syntax(), r.range),
307                            vec![make.name_ref(&index.to_string()).syntax().clone().into()],
308                        );
309                    }
310                }
311            }
312
313            builder.add_file_edits(file_id.file_id(ctx.db()), editor);
314        }
315    }
316}
317
318fn delete_whitespace(edit: &SyntaxEditor, whitespace: Option<impl Element>) {
319    let Some(whitespace) = whitespace else { return };
320    let NodeOrToken::Token(token) = whitespace.syntax_element() else { return };
321
322    if token.kind() == SyntaxKind::WHITESPACE && !token.text().contains('\n') {
323        edit.delete(token);
324    }
325}
326
327fn remove_trailing_comma(w: ast::WhereClause) -> SyntaxNode {
328    let (editor, w) = SyntaxEditor::new(w.syntax().clone());
329    if let Some(last) = w.last_child_or_token()
330        && last.kind() == T![,]
331    {
332        editor.delete(last);
333    }
334    editor.finish().new_root().clone()
335}
336
337#[cfg(test)]
338mod tests {
339    use crate::tests::{check_assist, check_assist_not_applicable};
340
341    use super::*;
342
343    #[test]
344    fn not_applicable_other_than_record_struct() {
345        check_assist_not_applicable(convert_named_struct_to_tuple_struct, r#"struct Foo$0(u32)"#);
346        check_assist_not_applicable(convert_named_struct_to_tuple_struct, r#"struct Foo$0;"#);
347    }
348
349    #[test]
350    fn convert_simple_struct() {
351        check_assist(
352            convert_named_struct_to_tuple_struct,
353            r#"
354struct Inner;
355struct A$0 { inner: Inner }
356
357impl A {
358    fn new(inner: Inner) -> A {
359        A { inner }
360    }
361
362    fn new_with_default() -> A {
363        A::new(Inner)
364    }
365
366    fn into_inner(self) -> Inner {
367        self.inner
368    }
369}"#,
370            r#"
371struct Inner;
372struct A(Inner);
373
374impl A {
375    fn new(inner: Inner) -> A {
376        A(inner)
377    }
378
379    fn new_with_default() -> A {
380        A::new(Inner)
381    }
382
383    fn into_inner(self) -> Inner {
384        self.0
385    }
386}"#,
387        );
388    }
389
390    #[test]
391    fn convert_simple_struct_cursor_on_struct_keyword() {
392        check_assist(
393            convert_named_struct_to_tuple_struct,
394            r#"
395struct Inner;
396struct$0 A { inner: Inner }
397
398impl A {
399    fn new(inner: Inner) -> A {
400        A { inner }
401    }
402
403    fn new_with_default() -> A {
404        A::new(Inner)
405    }
406
407    fn into_inner(self) -> Inner {
408        self.inner
409    }
410}"#,
411            r#"
412struct Inner;
413struct A(Inner);
414
415impl A {
416    fn new(inner: Inner) -> A {
417        A(inner)
418    }
419
420    fn new_with_default() -> A {
421        A::new(Inner)
422    }
423
424    fn into_inner(self) -> Inner {
425        self.0
426    }
427}"#,
428        );
429    }
430
431    #[test]
432    fn convert_struct_and_rest_pat() {
433        check_assist(
434            convert_named_struct_to_tuple_struct,
435            r#"
436struct Inner;
437struct A$0 { inner: Inner }
438fn foo(A { .. }: A) {}
439"#,
440            r#"
441struct Inner;
442struct A(Inner);
443fn foo(A(..): A) {}
444"#,
445        );
446
447        check_assist(
448            convert_named_struct_to_tuple_struct,
449            r#"
450struct Inner;
451struct A$0 { inner: Inner, extra: Inner }
452fn foo(A { inner, .. }: A) {}
453"#,
454            r#"
455struct Inner;
456struct A(Inner, Inner);
457fn foo(A(inner, ..): A) {}
458"#,
459        );
460    }
461
462    #[test]
463    fn convert_simple_struct_cursor_on_visibility_keyword() {
464        check_assist(
465            convert_named_struct_to_tuple_struct,
466            r#"
467struct Inner;
468pub$0 struct A { inner: Inner }
469
470impl A {
471    fn new(inner: Inner) -> A {
472        A { inner }
473    }
474
475    fn new_with_default() -> A {
476        A::new(Inner)
477    }
478
479    fn into_inner(self) -> Inner {
480        self.inner
481    }
482}"#,
483            r#"
484struct Inner;
485pub struct A(Inner);
486
487impl A {
488    fn new(inner: Inner) -> A {
489        A(inner)
490    }
491
492    fn new_with_default() -> A {
493        A::new(Inner)
494    }
495
496    fn into_inner(self) -> Inner {
497        self.0
498    }
499}"#,
500        );
501    }
502
503    #[test]
504    fn convert_struct_referenced_via_self_kw() {
505        check_assist(
506            convert_named_struct_to_tuple_struct,
507            r#"
508struct Inner;
509struct A$0 { inner: Inner }
510
511impl A {
512    fn new(inner: Inner) -> Self {
513        Self { inner }
514    }
515
516    fn new_with_default() -> Self {
517        Self::new(Inner)
518    }
519
520    fn into_inner(self) -> Inner {
521        self.inner
522    }
523}"#,
524            r#"
525struct Inner;
526struct A(Inner);
527
528impl A {
529    fn new(inner: Inner) -> Self {
530        Self(inner)
531    }
532
533    fn new_with_default() -> Self {
534        Self::new(Inner)
535    }
536
537    fn into_inner(self) -> Inner {
538        self.0
539    }
540}"#,
541        );
542    }
543
544    #[test]
545    fn convert_destructured_struct() {
546        check_assist(
547            convert_named_struct_to_tuple_struct,
548            r#"
549struct Inner;
550struct A$0 { inner: Inner }
551
552impl A {
553    fn into_inner(self) -> Inner {
554        let A { inner: a } = self;
555        a
556    }
557
558    fn into_inner_via_self(self) -> Inner {
559        let Self { inner } = self;
560        inner
561    }
562}"#,
563            r#"
564struct Inner;
565struct A(Inner);
566
567impl A {
568    fn into_inner(self) -> Inner {
569        let A(a) = self;
570        a
571    }
572
573    fn into_inner_via_self(self) -> Inner {
574        let Self(inner) = self;
575        inner
576    }
577}"#,
578        );
579    }
580
581    #[test]
582    fn convert_struct_with_visibility() {
583        check_assist(
584            convert_named_struct_to_tuple_struct,
585            r#"
586struct A$0 {
587    pub first: u32,
588    pub(crate) second: u64
589}
590
591impl A {
592    fn new() -> A {
593        A { first: 42, second: 42 }
594    }
595
596    fn into_first(self) -> u32 {
597        self.first
598    }
599
600    fn into_second(self) -> u64 {
601        self.second
602    }
603}"#,
604            r#"
605struct A(pub u32, pub(crate) u64);
606
607impl A {
608    fn new() -> A {
609        A(42, 42)
610    }
611
612    fn into_first(self) -> u32 {
613        self.0
614    }
615
616    fn into_second(self) -> u64 {
617        self.1
618    }
619}"#,
620        );
621    }
622
623    #[test]
624    fn convert_struct_with_wrapped_references() {
625        check_assist(
626            convert_named_struct_to_tuple_struct,
627            r#"
628struct Inner$0 { uint: u32 }
629struct Outer { inner: Inner }
630
631impl Outer {
632    fn new() -> Self {
633        Self { inner: Inner { uint: 42 } }
634    }
635
636    fn into_inner(self) -> u32 {
637        self.inner.uint
638    }
639
640    fn into_inner_destructed(self) -> u32 {
641        let Outer { inner: Inner { uint: x } } = self;
642        x
643    }
644}"#,
645            r#"
646struct Inner(u32);
647struct Outer { inner: Inner }
648
649impl Outer {
650    fn new() -> Self {
651        Self { inner: Inner(42) }
652    }
653
654    fn into_inner(self) -> u32 {
655        self.inner.0
656    }
657
658    fn into_inner_destructed(self) -> u32 {
659        let Outer { inner: Inner(x) } = self;
660        x
661    }
662}"#,
663        );
664
665        check_assist(
666            convert_named_struct_to_tuple_struct,
667            r#"
668struct Inner { uint: u32 }
669struct Outer$0 { inner: Inner }
670
671impl Outer {
672    fn new() -> Self {
673        Self { inner: Inner { uint: 42 } }
674    }
675
676    fn into_inner(self) -> u32 {
677        self.inner.uint
678    }
679
680    fn into_inner_destructed(self) -> u32 {
681        let Outer { inner: Inner { uint: x } } = self;
682        x
683    }
684}"#,
685            r#"
686struct Inner { uint: u32 }
687struct Outer(Inner);
688
689impl Outer {
690    fn new() -> Self {
691        Self(Inner { uint: 42 })
692    }
693
694    fn into_inner(self) -> u32 {
695        self.0.uint
696    }
697
698    fn into_inner_destructed(self) -> u32 {
699        let Outer(Inner { uint: x }) = self;
700        x
701    }
702}"#,
703        );
704    }
705
706    #[test]
707    fn convert_struct_with_multi_file_references() {
708        check_assist(
709            convert_named_struct_to_tuple_struct,
710            r#"
711//- /main.rs
712struct Inner;
713struct A$0 { inner: Inner }
714
715mod foo;
716
717//- /foo.rs
718use crate::{A, Inner};
719fn f() {
720    let a = A { inner: Inner };
721}
722"#,
723            r#"
724//- /main.rs
725struct Inner;
726struct A(Inner);
727
728mod foo;
729
730//- /foo.rs
731use crate::{A, Inner};
732fn f() {
733    let a = A(Inner);
734}
735"#,
736        );
737    }
738
739    #[test]
740    fn convert_struct_with_where_clause() {
741        check_assist(
742            convert_named_struct_to_tuple_struct,
743            r#"
744struct Wrap$0<T>
745where
746    T: Display,
747{ field1: T }
748"#,
749            r#"
750struct Wrap<T>(T)
751where
752    T: Display;
753
754"#,
755        );
756    }
757
758    #[test]
759    fn convert_constructor_expr_uses_self() {
760        // regression test for #21595
761        check_assist(
762            convert_named_struct_to_tuple_struct,
763            r#"
764struct $0Foo { field1: u32 }
765impl Foo {
766    fn clone(&self) -> Self {
767        Self { field1: self.field1 }
768    }
769}"#,
770            r#"
771struct Foo(u32);
772impl Foo {
773    fn clone(&self) -> Self {
774        Self(self.0)
775    }
776}"#,
777        );
778
779        check_assist(
780            convert_named_struct_to_tuple_struct,
781            r#"
782macro_rules! id {
783    ($($t:tt)*) => { $($t)* }
784}
785struct $0Foo { field1: u32 }
786impl Foo {
787    fn clone(&self) -> Self {
788        id!(Self { field1: self.field1 })
789    }
790}"#,
791            r#"
792macro_rules! id {
793    ($($t:tt)*) => { $($t)* }
794}
795struct Foo(u32);
796impl Foo {
797    fn clone(&self) -> Self {
798        id!(Self(self.0))
799    }
800}"#,
801        );
802    }
803
804    #[test]
805    fn convert_pat_uses_self() {
806        // regression test for #21595
807        check_assist(
808            convert_named_struct_to_tuple_struct,
809            r#"
810enum Foo {
811    $0Value { field: &'static Foo },
812    Nil,
813}
814fn foo(foo: &Foo) {
815    if let Foo::Value { field: Foo::Value { field } } = foo {}
816}"#,
817            r#"
818enum Foo {
819    Value(&'static Foo),
820    Nil,
821}
822fn foo(foo: &Foo) {
823    if let Foo::Value(Foo::Value(field)) = foo {}
824}"#,
825        );
826
827        check_assist(
828            convert_named_struct_to_tuple_struct,
829            r#"
830macro_rules! id {
831    ($($t:tt)*) => { $($t)* }
832}
833enum Foo {
834    $0Value { field: &'static Foo },
835    Nil,
836}
837fn foo(foo: &Foo) {
838    if let id!(Foo::Value { field: Foo::Value { field } }) = foo {}
839}"#,
840            r#"
841macro_rules! id {
842    ($($t:tt)*) => { $($t)* }
843}
844enum Foo {
845    Value(&'static Foo),
846    Nil,
847}
848fn foo(foo: &Foo) {
849    if let id!(Foo::Value(Foo::Value(field))) = foo {}
850}"#,
851        );
852    }
853
854    #[test]
855    fn not_applicable_other_than_record_variant() {
856        check_assist_not_applicable(
857            convert_named_struct_to_tuple_struct,
858            r#"enum Enum { Variant$0(usize) };"#,
859        );
860        check_assist_not_applicable(
861            convert_named_struct_to_tuple_struct,
862            r#"enum Enum { Variant$0 }"#,
863        );
864    }
865
866    #[test]
867    fn convert_simple_variant() {
868        check_assist(
869            convert_named_struct_to_tuple_struct,
870            r#"
871enum A {
872    $0Variant { field1: usize },
873}
874
875impl A {
876    fn new(value: usize) -> A {
877        A::Variant { field1: value }
878    }
879
880    fn new_with_default() -> A {
881        A::new(Default::default())
882    }
883
884    fn value(self) -> usize {
885        match self {
886            A::Variant { field1: value } => value,
887        }
888    }
889}"#,
890            r#"
891enum A {
892    Variant(usize),
893}
894
895impl A {
896    fn new(value: usize) -> A {
897        A::Variant(value)
898    }
899
900    fn new_with_default() -> A {
901        A::new(Default::default())
902    }
903
904    fn value(self) -> usize {
905        match self {
906            A::Variant(value) => value,
907        }
908    }
909}"#,
910        );
911    }
912
913    #[test]
914    fn convert_variant_referenced_via_self_kw() {
915        check_assist(
916            convert_named_struct_to_tuple_struct,
917            r#"
918enum A {
919    $0Variant { field1: usize },
920}
921
922impl A {
923    fn new(value: usize) -> A {
924        Self::Variant { field1: value }
925    }
926
927    fn new_with_default() -> A {
928        Self::new(Default::default())
929    }
930
931    fn value(self) -> usize {
932        match self {
933            Self::Variant { field1: value } => value,
934        }
935    }
936}"#,
937            r#"
938enum A {
939    Variant(usize),
940}
941
942impl A {
943    fn new(value: usize) -> A {
944        Self::Variant(value)
945    }
946
947    fn new_with_default() -> A {
948        Self::new(Default::default())
949    }
950
951    fn value(self) -> usize {
952        match self {
953            Self::Variant(value) => value,
954        }
955    }
956}"#,
957        );
958    }
959
960    #[test]
961    fn convert_destructured_variant() {
962        check_assist(
963            convert_named_struct_to_tuple_struct,
964            r#"
965enum A {
966    $0Variant { field1: usize },
967}
968
969impl A {
970    fn into_inner(self) -> usize {
971        let A::Variant { field1: first } = self;
972        first
973    }
974
975    fn into_inner_via_self(self) -> usize {
976        let Self::Variant { field1: first } = self;
977        first
978    }
979}"#,
980            r#"
981enum A {
982    Variant(usize),
983}
984
985impl A {
986    fn into_inner(self) -> usize {
987        let A::Variant(first) = self;
988        first
989    }
990
991    fn into_inner_via_self(self) -> usize {
992        let Self::Variant(first) = self;
993        first
994    }
995}"#,
996        );
997    }
998
999    #[test]
1000    fn convert_variant_with_wrapped_references() {
1001        check_assist(
1002            convert_named_struct_to_tuple_struct,
1003            r#"
1004enum Inner {
1005    $0Variant { field1: usize },
1006}
1007enum Outer {
1008    Variant(Inner),
1009}
1010
1011impl Outer {
1012    fn new() -> Self {
1013        Self::Variant(Inner::Variant { field1: 42 })
1014    }
1015
1016    fn into_inner_destructed(self) -> u32 {
1017        let Outer::Variant(Inner::Variant { field1: x }) = self;
1018        x
1019    }
1020}"#,
1021            r#"
1022enum Inner {
1023    Variant(usize),
1024}
1025enum Outer {
1026    Variant(Inner),
1027}
1028
1029impl Outer {
1030    fn new() -> Self {
1031        Self::Variant(Inner::Variant(42))
1032    }
1033
1034    fn into_inner_destructed(self) -> u32 {
1035        let Outer::Variant(Inner::Variant(x)) = self;
1036        x
1037    }
1038}"#,
1039        );
1040
1041        check_assist(
1042            convert_named_struct_to_tuple_struct,
1043            r#"
1044enum Inner {
1045    Variant(usize),
1046}
1047enum Outer {
1048    $0Variant { field1: Inner },
1049}
1050
1051impl Outer {
1052    fn new() -> Self {
1053        Self::Variant { field1: Inner::Variant(42) }
1054    }
1055
1056    fn into_inner_destructed(self) -> u32 {
1057        let Outer::Variant { field1: Inner::Variant(x) } = self;
1058        x
1059    }
1060}"#,
1061            r#"
1062enum Inner {
1063    Variant(usize),
1064}
1065enum Outer {
1066    Variant(Inner),
1067}
1068
1069impl Outer {
1070    fn new() -> Self {
1071        Self::Variant(Inner::Variant(42))
1072    }
1073
1074    fn into_inner_destructed(self) -> u32 {
1075        let Outer::Variant(Inner::Variant(x)) = self;
1076        x
1077    }
1078}"#,
1079        );
1080    }
1081
1082    #[test]
1083    fn convert_variant_with_multi_file_references() {
1084        check_assist(
1085            convert_named_struct_to_tuple_struct,
1086            r#"
1087//- /main.rs
1088struct Inner;
1089enum A {
1090    $0Variant { field1: Inner },
1091}
1092
1093mod foo;
1094
1095//- /foo.rs
1096use crate::{A, Inner};
1097fn f() {
1098    let a = A::Variant { field1: Inner };
1099}
1100"#,
1101            r#"
1102//- /main.rs
1103struct Inner;
1104enum A {
1105    Variant(Inner),
1106}
1107
1108mod foo;
1109
1110//- /foo.rs
1111use crate::{A, Inner};
1112fn f() {
1113    let a = A::Variant(Inner);
1114}
1115"#,
1116        );
1117    }
1118
1119    #[test]
1120    fn convert_directly_used_variant() {
1121        check_assist(
1122            convert_named_struct_to_tuple_struct,
1123            r#"
1124//- /main.rs
1125struct Inner;
1126enum A {
1127    $0Variant { field1: Inner },
1128}
1129
1130mod foo;
1131
1132//- /foo.rs
1133use crate::{A::Variant, Inner};
1134fn f() {
1135    let a = Variant { field1: Inner };
1136}
1137"#,
1138            r#"
1139//- /main.rs
1140struct Inner;
1141enum A {
1142    Variant(Inner),
1143}
1144
1145mod foo;
1146
1147//- /foo.rs
1148use crate::{A::Variant, Inner};
1149fn f() {
1150    let a = Variant(Inner);
1151}
1152"#,
1153        );
1154    }
1155
1156    #[test]
1157    fn field_access_inside_macro_call() {
1158        check_assist(
1159            convert_named_struct_to_tuple_struct,
1160            r#"
1161struct $0Struct {
1162    inner: i32,
1163}
1164
1165macro_rules! id {
1166    ($e:expr) => { $e }
1167}
1168
1169fn test(c: Struct) {
1170    id!(c.inner);
1171}
1172"#,
1173            r#"
1174struct Struct(i32);
1175
1176macro_rules! id {
1177    ($e:expr) => { $e }
1178}
1179
1180fn test(c: Struct) {
1181    id!(c.0);
1182}
1183"#,
1184        )
1185    }
1186
1187    #[test]
1188    fn struct_usage_inside_macro_call() {
1189        check_assist(
1190            convert_named_struct_to_tuple_struct,
1191            r#"
1192macro_rules! id {
1193    ($($t:tt)*) => { $($t)* }
1194}
1195
1196struct $0Struct {
1197    inner: i32,
1198}
1199
1200fn test() {
1201    id! {
1202        let s = Struct {
1203            inner: 42,
1204        };
1205        let Struct { inner: value } = s;
1206        let Struct { inner } = s;
1207    }
1208}
1209"#,
1210            r#"
1211macro_rules! id {
1212    ($($t:tt)*) => { $($t)* }
1213}
1214
1215struct Struct(i32);
1216
1217fn test() {
1218    id! {
1219        let s = Struct(
1220            42,
1221        );
1222        let Struct(value) = s;
1223        let Struct(inner) = s;
1224    }
1225}
1226"#,
1227        );
1228    }
1229
1230    #[test]
1231    fn struct_name_ref_may_not_be_part_of_struct_expr_or_struct_pat() {
1232        check_assist(
1233            convert_named_struct_to_tuple_struct,
1234            r#"
1235struct $0Struct {
1236    inner: i32,
1237}
1238struct Outer<T> {
1239    value: T,
1240}
1241fn foo<T>() -> T { loop {} }
1242
1243fn test() {
1244    Outer {
1245        value: foo::<Struct>();
1246    }
1247}
1248
1249trait HasAssoc {
1250    type Assoc;
1251    fn test();
1252}
1253impl HasAssoc for Struct {
1254    type Assoc = Outer<i32>;
1255    fn test() {
1256        let a = Self::Assoc {
1257            value: 42,
1258        };
1259        let Self::Assoc { value } = a;
1260    }
1261}
1262"#,
1263            r#"
1264struct Struct(i32);
1265struct Outer<T> {
1266    value: T,
1267}
1268fn foo<T>() -> T { loop {} }
1269
1270fn test() {
1271    Outer {
1272        value: foo::<Struct>();
1273    }
1274}
1275
1276trait HasAssoc {
1277    type Assoc;
1278    fn test();
1279}
1280impl HasAssoc for Struct {
1281    type Assoc = Outer<i32>;
1282    fn test() {
1283        let a = Self::Assoc {
1284            value: 42,
1285        };
1286        let Self::Assoc { value } = a;
1287    }
1288}
1289"#,
1290        );
1291    }
1292
1293    #[test]
1294    fn fields_with_attrs() {
1295        check_assist(
1296            convert_named_struct_to_tuple_struct,
1297            r#"
1298pub struct $0Foo {
1299    #[my_custom_attr]
1300    value: u32,
1301}
1302"#,
1303            r#"
1304pub struct Foo(#[my_custom_attr]u32);
1305"#,
1306        );
1307    }
1308}