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
15pub(crate) fn convert_named_struct_to_tuple_struct(
56 acc: &mut Assists,
57 ctx: &AssistContext<'_, '_>,
58) -> Option<()> {
59 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 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 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 let name_ref = r.name.as_name_ref()?;
185 let path_segment = name_ref.syntax().parent().and_then(ast::PathSegment::cast)?;
186 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 return None;
194 }
195
196 let parent = full_path.syntax().parent()?;
198 match_ast! {
199 match parent {
200 ast::RecordPat(record_struct_pat) => {
201 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 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 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 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 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 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}