Skip to main content

ide_assists/handlers/
convert_tuple_struct_to_named_struct.rs

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