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
20pub(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 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 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 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 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}