1use std::iter;
2
3use either::Either;
4use hir::{HasCrate, Module, ModuleDef, Name, Variant};
5use ide_db::{
6 FxHashSet, RootDatabase,
7 defs::Definition,
8 helpers::mod_path_to_ast,
9 imports::insert_use::{ImportScope, InsertUseConfig, insert_use},
10 path_transform::PathTransform,
11 search::FileReference,
12};
13use itertools::Itertools;
14use syntax::{
15 Edition, SyntaxElement,
16 SyntaxKind::*,
17 SyntaxNode, T,
18 ast::{
19 self, AstNode, HasAttrs, HasGenericParams, HasName, HasVisibility,
20 edit::{AstNodeEdit, IndentLevel},
21 make,
22 },
23 match_ast, ted,
24};
25
26use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder};
27
28pub(crate) fn extract_struct_from_enum_variant(
42 acc: &mut Assists,
43 ctx: &AssistContext<'_>,
44) -> Option<()> {
45 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
46 let field_list = extract_field_list_if_applicable(&variant)?;
47
48 let variant_name = variant.name()?;
49 let variant_hir = ctx.sema.to_def(&variant)?;
50 if existing_definition(ctx.db(), &variant_name, &variant_hir) {
51 cov_mark::hit!(test_extract_enum_not_applicable_if_struct_exists);
52 return None;
53 }
54
55 let enum_ast = variant.parent_enum();
56 let enum_hir = ctx.sema.to_def(&enum_ast)?;
57 let target = variant.syntax().text_range();
58 acc.add(
59 AssistId::refactor_rewrite("extract_struct_from_enum_variant"),
60 "Extract struct from enum variant",
61 target,
62 |builder| {
63 let edition = enum_hir.krate(ctx.db()).edition(ctx.db());
64 let variant_hir_name = variant_hir.name(ctx.db());
65 let enum_module_def = ModuleDef::from(enum_hir);
66 let usages = Definition::Variant(variant_hir).usages(&ctx.sema).all();
67
68 let mut visited_modules_set = FxHashSet::default();
69 let current_module = enum_hir.module(ctx.db());
70 visited_modules_set.insert(current_module);
71 let mut def_file_references = None;
73 for (file_id, references) in usages {
74 if file_id == ctx.file_id() {
75 def_file_references = Some(references);
76 continue;
77 }
78 builder.edit_file(file_id.file_id(ctx.db()));
79 let processed = process_references(
80 ctx,
81 builder,
82 &mut visited_modules_set,
83 &enum_module_def,
84 &variant_hir_name,
85 references,
86 );
87 processed.into_iter().for_each(|(path, node, import)| {
88 apply_references(ctx.config.insert_use, path, node, import, edition)
89 });
90 }
91 builder.edit_file(ctx.vfs_file_id());
92
93 let variant = builder.make_mut(variant.clone());
94 if let Some(references) = def_file_references {
95 let processed = process_references(
96 ctx,
97 builder,
98 &mut visited_modules_set,
99 &enum_module_def,
100 &variant_hir_name,
101 references,
102 );
103 processed.into_iter().for_each(|(path, node, import)| {
104 apply_references(ctx.config.insert_use, path, node, import, edition)
105 });
106 }
107
108 let generic_params = enum_ast
109 .generic_param_list()
110 .and_then(|known_generics| extract_generic_params(&known_generics, &field_list));
111 let generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
112
113 let field_list = if let Some((target_scope, source_scope)) =
115 ctx.sema.scope(enum_ast.syntax()).zip(ctx.sema.scope(field_list.syntax()))
116 {
117 let field_list = field_list.reset_indent();
118 let field_list =
119 PathTransform::generic_transformation(&target_scope, &source_scope)
120 .apply(field_list.syntax());
121 match_ast! {
122 match field_list {
123 ast::RecordFieldList(field_list) => Either::Left(field_list),
124 ast::TupleFieldList(field_list) => Either::Right(field_list),
125 _ => unreachable!(),
126 }
127 }
128 } else {
129 field_list.clone_for_update()
130 };
131
132 let def =
133 create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast);
134
135 let enum_ast = variant.parent_enum();
136 let indent = enum_ast.indent_level();
137 let def = def.indent(indent);
138
139 ted::insert_all(
140 ted::Position::before(enum_ast.syntax()),
141 vec![
142 def.syntax().clone().into(),
143 make::tokens::whitespace(&format!("\n\n{indent}")).into(),
144 ],
145 );
146
147 update_variant(&variant, generic_params.map(|g| g.clone_for_update()));
148 },
149 )
150}
151
152fn extract_field_list_if_applicable(
153 variant: &ast::Variant,
154) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
155 match variant.kind() {
156 ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
157 Some(Either::Left(field_list))
158 }
159 ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
160 Some(Either::Right(field_list))
161 }
162 _ => None,
163 }
164}
165
166fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Variant) -> bool {
167 variant
168 .parent_enum(db)
169 .module(db)
170 .scope(db, None)
171 .into_iter()
172 .filter(|(_, def)| match def {
173 hir::ScopeDef::ModuleDef(def) => matches!(
175 def,
176 ModuleDef::Module(_)
177 | ModuleDef::Adt(_)
178 | ModuleDef::Variant(_)
179 | ModuleDef::Trait(_)
180 | ModuleDef::TypeAlias(_)
181 | ModuleDef::BuiltinType(_)
182 ),
183 _ => false,
184 })
185 .any(|(name, _)| name.as_str() == variant_name.text().trim_start_matches("r#"))
186}
187
188fn extract_generic_params(
189 known_generics: &ast::GenericParamList,
190 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
191) -> Option<ast::GenericParamList> {
192 let mut generics = known_generics.generic_params().map(|param| (param, false)).collect_vec();
193
194 let tagged_one = match field_list {
195 Either::Left(field_list) => field_list
196 .fields()
197 .filter_map(|f| f.ty())
198 .fold(false, |tagged, ty| tag_generics_in_variant(&ty, &mut generics) || tagged),
199 Either::Right(field_list) => field_list
200 .fields()
201 .filter_map(|f| f.ty())
202 .fold(false, |tagged, ty| tag_generics_in_variant(&ty, &mut generics) || tagged),
203 };
204
205 let generics = generics.into_iter().filter_map(|(param, tag)| tag.then_some(param));
206 tagged_one.then(|| make::generic_param_list(generics))
207}
208
209fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, bool)]) -> bool {
210 let mut tagged_one = false;
211
212 for token in ty.syntax().descendants_with_tokens().filter_map(SyntaxElement::into_token) {
213 for (param, tag) in generics.iter_mut().filter(|(_, tag)| !tag) {
214 match param {
215 ast::GenericParam::LifetimeParam(lt)
216 if matches!(token.kind(), T![lifetime_ident]) =>
217 {
218 if let Some(lt) = lt.lifetime()
219 && lt.text().as_str() == token.text()
220 {
221 *tag = true;
222 tagged_one = true;
223 break;
224 }
225 }
226 param if matches!(token.kind(), T![ident]) => {
227 if match param {
228 ast::GenericParam::ConstParam(konst) => konst
229 .name()
230 .map(|name| name.text().as_str() == token.text())
231 .unwrap_or_default(),
232 ast::GenericParam::TypeParam(ty) => ty
233 .name()
234 .map(|name| name.text().as_str() == token.text())
235 .unwrap_or_default(),
236 ast::GenericParam::LifetimeParam(lt) => lt
237 .lifetime()
238 .map(|lt| lt.text().as_str() == token.text())
239 .unwrap_or_default(),
240 } {
241 *tag = true;
242 tagged_one = true;
243 break;
244 }
245 }
246 _ => (),
247 }
248 }
249 }
250
251 tagged_one
252}
253
254fn create_struct_def(
255 name: ast::Name,
256 variant: &ast::Variant,
257 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
258 generics: Option<ast::GenericParamList>,
259 enum_: &ast::Enum,
260) -> ast::Struct {
261 let enum_vis = enum_.visibility();
262
263 let insert_vis = |node: &'_ SyntaxNode, vis: &'_ SyntaxNode| {
264 let vis = vis.clone_for_update();
265 ted::insert(ted::Position::before(node), vis);
266 };
267
268 let field_list: ast::FieldList = match field_list {
270 Either::Left(field_list) => {
271 if let Some(vis) = &enum_vis {
272 field_list
273 .fields()
274 .filter(|field| field.visibility().is_none())
275 .filter_map(|field| field.name())
276 .for_each(|it| insert_vis(it.syntax(), vis.syntax()));
277 }
278
279 field_list.clone().into()
280 }
281 Either::Right(field_list) => {
282 if let Some(vis) = &enum_vis {
283 field_list
284 .fields()
285 .filter(|field| field.visibility().is_none())
286 .filter_map(|field| field.ty())
287 .for_each(|it| insert_vis(it.syntax(), vis.syntax()));
288 }
289
290 field_list.clone().into()
291 }
292 };
293 let field_list = field_list.indent(IndentLevel::single());
294
295 let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update();
296
297 ted::insert_all(
299 ted::Position::first_child_of(strukt.syntax()),
300 take_all_comments(variant.syntax()),
301 );
302
303 ted::insert_all(
305 ted::Position::first_child_of(strukt.syntax()),
306 enum_
307 .attrs()
308 .flat_map(|it| {
309 vec![it.syntax().clone_for_update().into(), make::tokens::single_newline().into()]
310 })
311 .collect(),
312 );
313
314 strukt
315}
316
317fn update_variant(variant: &ast::Variant, generics: Option<ast::GenericParamList>) -> Option<()> {
318 let name = variant.name()?;
319 let generic_args = generics
320 .filter(|generics| generics.generic_params().count() > 0)
321 .map(|generics| generics.to_generic_args());
322 let ty = match generic_args {
324 Some(generic_args) => make::ty(&format!("{name}{generic_args}")),
325 None => make::ty(&name.text()),
326 };
327
328 let tuple_field = make::tuple_field(None, ty);
330 let field_list = make::tuple_field_list(iter::once(tuple_field)).clone_for_update();
331 ted::replace(variant.field_list()?.syntax(), field_list.syntax());
332
333 if let Some(ws) = name
335 .syntax()
336 .siblings_with_tokens(syntax::Direction::Next)
337 .find_map(|tok| tok.into_token().filter(|tok| tok.kind() == WHITESPACE))
338 {
339 ted::remove(SyntaxElement::Token(ws));
340 }
341
342 Some(())
343}
344
345fn take_all_comments(node: &SyntaxNode) -> Vec<SyntaxElement> {
349 let mut remove_next_ws = false;
350 node.children_with_tokens()
351 .filter_map(move |child| match child.kind() {
352 COMMENT => {
353 remove_next_ws = true;
354 child.detach();
355 Some(child)
356 }
357 WHITESPACE if remove_next_ws => {
358 remove_next_ws = false;
359 child.detach();
360 Some(make::tokens::single_newline().into())
361 }
362 _ => {
363 remove_next_ws = false;
364 None
365 }
366 })
367 .collect()
368}
369
370fn apply_references(
371 insert_use_cfg: InsertUseConfig,
372 segment: ast::PathSegment,
373 node: SyntaxNode,
374 import: Option<(ImportScope, hir::ModPath)>,
375 edition: Edition,
376) {
377 if let Some((scope, path)) = import {
378 insert_use(&scope, mod_path_to_ast(&path, edition), &insert_use_cfg);
379 }
380 let path = make::path_from_segments(iter::once(segment.clone_subtree()), false);
382 ted::insert_raw(ted::Position::before(segment.syntax()), path.clone_for_update().syntax());
383 ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
384 ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
385}
386
387fn process_references(
388 ctx: &AssistContext<'_>,
389 builder: &mut SourceChangeBuilder,
390 visited_modules: &mut FxHashSet<Module>,
391 enum_module_def: &ModuleDef,
392 variant_hir_name: &Name,
393 refs: Vec<FileReference>,
394) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> {
395 refs.into_iter()
398 .flat_map(|reference| {
399 let (segment, scope_node, module) = reference_to_node(&ctx.sema, reference)?;
400 let segment = builder.make_mut(segment);
401 let scope_node = builder.make_syntax_mut(scope_node);
402 if !visited_modules.contains(&module) {
403 let cfg =
404 ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
405 let mod_path = module.find_use_path(
406 ctx.sema.db,
407 *enum_module_def,
408 ctx.config.insert_use.prefix_kind,
409 cfg,
410 );
411 if let Some(mut mod_path) = mod_path {
412 mod_path.pop_segment();
413 mod_path.push_segment(variant_hir_name.clone());
414 let scope = ImportScope::find_insert_use_container(&scope_node, &ctx.sema)?;
415 visited_modules.insert(module);
416 return Some((segment, scope_node, Some((scope, mod_path))));
417 }
418 }
419 Some((segment, scope_node, None))
420 })
421 .collect()
422}
423
424fn reference_to_node(
425 sema: &hir::Semantics<'_, RootDatabase>,
426 reference: FileReference,
427) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
428 let segment =
429 reference.name.as_name_ref()?.syntax().parent().and_then(ast::PathSegment::cast)?;
430
431 let segment_range = segment.syntax().text_range();
433 if segment_range != reference.range {
434 return None;
435 }
436
437 let parent = segment.parent_path().syntax().parent()?;
438 let expr_or_pat = match_ast! {
439 match parent {
440 ast::PathExpr(_it) => parent.parent()?,
441 ast::RecordExpr(_it) => parent,
442 ast::TupleStructPat(_it) => parent,
443 ast::RecordPat(_it) => parent,
444 _ => return None,
445 }
446 };
447 let module = sema.scope(&expr_or_pat)?.module();
448 Some((segment, expr_or_pat, module))
449}
450
451#[cfg(test)]
452mod tests {
453 use crate::tests::{check_assist, check_assist_not_applicable};
454
455 use super::*;
456
457 #[test]
458 fn test_with_marco() {
459 check_assist(
460 extract_struct_from_enum_variant,
461 r#"
462macro_rules! foo {
463 ($x:expr) => {
464 $x
465 };
466}
467
468enum TheEnum {
469 TheVariant$0 { the_field: u8 },
470}
471
472fn main() {
473 foo![TheEnum::TheVariant { the_field: 42 }];
474}
475"#,
476 r#"
477macro_rules! foo {
478 ($x:expr) => {
479 $x
480 };
481}
482
483struct TheVariant { the_field: u8 }
484
485enum TheEnum {
486 TheVariant(TheVariant),
487}
488
489fn main() {
490 foo![TheEnum::TheVariant { the_field: 42 }];
491}
492"#,
493 );
494 }
495
496 #[test]
497 fn issue_16197() {
498 check_assist(
499 extract_struct_from_enum_variant,
500 r#"
501enum Foo {
502 Bar $0{ node: Box<Self> },
503 Nil,
504}
505"#,
506 r#"
507struct Bar { node: Box<Foo> }
508
509enum Foo {
510 Bar(Bar),
511 Nil,
512}
513"#,
514 );
515 check_assist(
516 extract_struct_from_enum_variant,
517 r#"
518enum Foo {
519 Bar $0{ node: Box<Self>, a: Arc<Box<Self>> },
520 Nil,
521}
522"#,
523 r#"
524struct Bar { node: Box<Foo>, a: Arc<Box<Foo>> }
525
526enum Foo {
527 Bar(Bar),
528 Nil,
529}
530"#,
531 );
532 check_assist(
533 extract_struct_from_enum_variant,
534 r#"
535enum Foo {
536 Nil(Box$0<Self>, Arc<Box<Self>>),
537}
538"#,
539 r#"
540struct Nil(Box<Foo>, Arc<Box<Foo>>);
541
542enum Foo {
543 Nil(Nil),
544}
545"#,
546 );
547 }
548
549 #[test]
550 fn test_extract_struct_several_fields_tuple() {
551 check_assist(
552 extract_struct_from_enum_variant,
553 "enum A { $0One(u32, u32) }",
554 r#"struct One(u32, u32);
555
556enum A { One(One) }"#,
557 );
558 }
559
560 #[test]
561 fn test_extract_struct_several_fields_named() {
562 check_assist(
563 extract_struct_from_enum_variant,
564 "enum A { $0One { foo: u32, bar: u32 } }",
565 r#"struct One { foo: u32, bar: u32 }
566
567enum A { One(One) }"#,
568 );
569 }
570
571 #[test]
572 fn test_extract_struct_one_field_named() {
573 check_assist(
574 extract_struct_from_enum_variant,
575 "enum A { $0One { foo: u32 } }",
576 r#"struct One { foo: u32 }
577
578enum A { One(One) }"#,
579 );
580 }
581
582 #[test]
583 fn test_extract_struct_carries_over_generics() {
584 check_assist(
585 extract_struct_from_enum_variant,
586 r"enum En<T> { Var { a: T$0 } }",
587 r#"struct Var<T> { a: T }
588
589enum En<T> { Var(Var<T>) }"#,
590 );
591 }
592
593 #[test]
594 fn test_extract_struct_carries_over_attributes() {
595 check_assist(
596 extract_struct_from_enum_variant,
597 r#"
598#[derive(Debug)]
599#[derive(Clone)]
600enum Enum { Variant{ field: u32$0 } }"#,
601 r#"
602#[derive(Debug)]
603#[derive(Clone)]
604struct Variant { field: u32 }
605
606#[derive(Debug)]
607#[derive(Clone)]
608enum Enum { Variant(Variant) }"#,
609 );
610 }
611
612 #[test]
613 fn test_extract_struct_indent_to_parent_enum() {
614 check_assist(
615 extract_struct_from_enum_variant,
616 r#"
617enum Enum {
618 Variant {
619 field: u32$0
620 }
621}"#,
622 r#"
623struct Variant {
624 field: u32
625}
626
627enum Enum {
628 Variant(Variant)
629}"#,
630 );
631 }
632
633 #[test]
634 fn test_extract_struct_indent_to_parent_enum_in_mod() {
635 check_assist(
636 extract_struct_from_enum_variant,
637 r#"
638mod indenting {
639 enum Enum {
640 Variant {
641 field: u32$0
642 }
643 }
644}"#,
645 r#"
646mod indenting {
647 struct Variant {
648 field: u32
649 }
650
651 enum Enum {
652 Variant(Variant)
653 }
654}"#,
655 );
656 }
657
658 #[test]
659 fn test_extract_struct_keep_comments_and_attrs_one_field_named() {
660 check_assist(
661 extract_struct_from_enum_variant,
662 r#"
663enum A {
664 $0One {
665 // leading comment
666 /// doc comment
667 #[an_attr]
668 foo: u32
669 // trailing comment
670 }
671}"#,
672 r#"
673struct One {
674 // leading comment
675 /// doc comment
676 #[an_attr]
677 foo: u32
678 // trailing comment
679}
680
681enum A {
682 One(One)
683}"#,
684 );
685 }
686
687 #[test]
688 fn test_extract_struct_keep_comments_and_attrs_several_fields_named() {
689 check_assist(
690 extract_struct_from_enum_variant,
691 r#"
692enum A {
693 $0One {
694 // comment
695 /// doc
696 #[attr]
697 foo: u32,
698 // comment
699 #[attr]
700 /// doc
701 bar: u32
702 }
703}"#,
704 r#"
705struct One {
706 // comment
707 /// doc
708 #[attr]
709 foo: u32,
710 // comment
711 #[attr]
712 /// doc
713 bar: u32
714}
715
716enum A {
717 One(One)
718}"#,
719 );
720 }
721
722 #[test]
723 fn test_extract_struct_keep_comments_and_attrs_several_fields_tuple() {
724 check_assist(
725 extract_struct_from_enum_variant,
726 "enum A { $0One(/* comment */ #[attr] u32, /* another */ u32 /* tail */) }",
727 r#"
728struct One(/* comment */ #[attr] u32, /* another */ u32 /* tail */);
729
730enum A { One(One) }"#,
731 );
732 }
733
734 #[test]
735 fn test_extract_struct_move_struct_variant_comments() {
736 check_assist(
737 extract_struct_from_enum_variant,
738 r#"
739enum A {
740 /* comment */
741 // other
742 /// comment
743 #[attr]
744 $0One {
745 a: u32
746 }
747}"#,
748 r#"
749/* comment */
750// other
751/// comment
752struct One {
753 a: u32
754}
755
756enum A {
757 #[attr]
758 One(One)
759}"#,
760 );
761 }
762
763 #[test]
764 fn test_extract_struct_move_tuple_variant_comments() {
765 check_assist(
766 extract_struct_from_enum_variant,
767 r#"
768enum A {
769 /* comment */
770 // other
771 /// comment
772 #[attr]
773 $0One(u32, u32)
774}"#,
775 r#"
776/* comment */
777// other
778/// comment
779struct One(u32, u32);
780
781enum A {
782 #[attr]
783 One(One)
784}"#,
785 );
786 }
787
788 #[test]
789 fn test_extract_struct_keep_existing_visibility_named() {
790 check_assist(
791 extract_struct_from_enum_variant,
792 "enum A { $0One{ a: u32, pub(crate) b: u32, pub(super) c: u32, d: u32 } }",
793 r#"
794struct One { a: u32, pub(crate) b: u32, pub(super) c: u32, d: u32 }
795
796enum A { One(One) }"#,
797 );
798 }
799
800 #[test]
801 fn test_extract_struct_keep_existing_visibility_tuple() {
802 check_assist(
803 extract_struct_from_enum_variant,
804 "enum A { $0One(u32, pub(crate) u32, pub(super) u32, u32) }",
805 r#"
806struct One(u32, pub(crate) u32, pub(super) u32, u32);
807
808enum A { One(One) }"#,
809 );
810 }
811
812 #[test]
813 fn test_extract_enum_variant_name_value_namespace() {
814 check_assist(
815 extract_struct_from_enum_variant,
816 r#"const One: () = ();
817enum A { $0One(u32, u32) }"#,
818 r#"const One: () = ();
819struct One(u32, u32);
820
821enum A { One(One) }"#,
822 );
823 }
824
825 #[test]
826 fn test_extract_struct_no_visibility() {
827 check_assist(
828 extract_struct_from_enum_variant,
829 "enum A { $0One(u32, u32) }",
830 r#"
831struct One(u32, u32);
832
833enum A { One(One) }"#,
834 );
835 }
836
837 #[test]
838 fn test_extract_struct_pub_visibility() {
839 check_assist(
840 extract_struct_from_enum_variant,
841 "pub enum A { $0One(u32, u32) }",
842 r#"
843pub struct One(pub u32, pub u32);
844
845pub enum A { One(One) }"#,
846 );
847 }
848
849 #[test]
850 fn test_extract_struct_pub_in_mod_visibility() {
851 check_assist(
852 extract_struct_from_enum_variant,
853 "pub(in something) enum A { $0One{ a: u32, b: u32 } }",
854 r#"
855pub(in something) struct One { pub(in something) a: u32, pub(in something) b: u32 }
856
857pub(in something) enum A { One(One) }"#,
858 );
859 }
860
861 #[test]
862 fn test_extract_struct_pub_crate_visibility() {
863 check_assist(
864 extract_struct_from_enum_variant,
865 "pub(crate) enum A { $0One{ a: u32, b: u32, c: u32 } }",
866 r#"
867pub(crate) struct One { pub(crate) a: u32, pub(crate) b: u32, pub(crate) c: u32 }
868
869pub(crate) enum A { One(One) }"#,
870 );
871 }
872
873 #[test]
874 fn test_extract_struct_with_complex_imports() {
875 check_assist(
876 extract_struct_from_enum_variant,
877 r#"mod my_mod {
878 fn another_fn() {
879 let m = my_other_mod::MyEnum::MyField(1, 1);
880 }
881
882 pub mod my_other_mod {
883 fn another_fn() {
884 let m = MyEnum::MyField(1, 1);
885 }
886
887 pub enum MyEnum {
888 $0MyField(u8, u8),
889 }
890 }
891}
892
893fn another_fn() {
894 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
895}"#,
896 r#"use my_mod::my_other_mod::MyField;
897
898mod my_mod {
899 use my_other_mod::MyField;
900
901 fn another_fn() {
902 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
903 }
904
905 pub mod my_other_mod {
906 fn another_fn() {
907 let m = MyEnum::MyField(MyField(1, 1));
908 }
909
910 pub struct MyField(pub u8, pub u8);
911
912 pub enum MyEnum {
913 MyField(MyField),
914 }
915 }
916}
917
918fn another_fn() {
919 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
920}"#,
921 );
922 }
923
924 #[test]
925 fn extract_record_fix_references() {
926 check_assist(
927 extract_struct_from_enum_variant,
928 r#"
929enum E {
930 $0V { i: i32, j: i32 }
931}
932
933fn f() {
934 let E::V { i, j } = E::V { i: 9, j: 2 };
935}
936"#,
937 r#"
938struct V { i: i32, j: i32 }
939
940enum E {
941 V(V)
942}
943
944fn f() {
945 let E::V(V { i, j }) = E::V(V { i: 9, j: 2 });
946}
947"#,
948 )
949 }
950
951 #[test]
952 fn extract_record_fix_references2() {
953 check_assist(
954 extract_struct_from_enum_variant,
955 r#"
956enum E {
957 $0V(i32, i32)
958}
959
960fn f() {
961 let E::V(i, j) = E::V(9, 2);
962}
963"#,
964 r#"
965struct V(i32, i32);
966
967enum E {
968 V(V)
969}
970
971fn f() {
972 let E::V(V(i, j)) = E::V(V(9, 2));
973}
974"#,
975 )
976 }
977
978 #[test]
979 fn test_several_files() {
980 check_assist(
981 extract_struct_from_enum_variant,
982 r#"
983//- /main.rs
984enum E {
985 $0V(i32, i32)
986}
987mod foo;
988
989//- /foo.rs
990use crate::E;
991fn f() {
992 let e = E::V(9, 2);
993}
994"#,
995 r#"
996//- /main.rs
997struct V(i32, i32);
998
999enum E {
1000 V(V)
1001}
1002mod foo;
1003
1004//- /foo.rs
1005use crate::{E, V};
1006fn f() {
1007 let e = E::V(V(9, 2));
1008}
1009"#,
1010 )
1011 }
1012
1013 #[test]
1014 fn test_several_files_record() {
1015 check_assist(
1016 extract_struct_from_enum_variant,
1017 r#"
1018//- /main.rs
1019enum E {
1020 $0V { i: i32, j: i32 }
1021}
1022mod foo;
1023
1024//- /foo.rs
1025use crate::E;
1026fn f() {
1027 let e = E::V { i: 9, j: 2 };
1028}
1029"#,
1030 r#"
1031//- /main.rs
1032struct V { i: i32, j: i32 }
1033
1034enum E {
1035 V(V)
1036}
1037mod foo;
1038
1039//- /foo.rs
1040use crate::{E, V};
1041fn f() {
1042 let e = E::V(V { i: 9, j: 2 });
1043}
1044"#,
1045 )
1046 }
1047
1048 #[test]
1049 fn test_extract_struct_record_nested_call_exp() {
1050 check_assist(
1051 extract_struct_from_enum_variant,
1052 r#"
1053enum A { $0One { a: u32, b: u32 } }
1054
1055struct B(A);
1056
1057fn foo() {
1058 let _ = B(A::One { a: 1, b: 2 });
1059}
1060"#,
1061 r#"
1062struct One { a: u32, b: u32 }
1063
1064enum A { One(One) }
1065
1066struct B(A);
1067
1068fn foo() {
1069 let _ = B(A::One(One { a: 1, b: 2 }));
1070}
1071"#,
1072 );
1073 }
1074
1075 #[test]
1076 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
1077 check_assist_not_applicable(extract_struct_from_enum_variant, r#"enum A { $0One }"#);
1078 }
1079
1080 #[test]
1081 fn test_extract_enum_not_applicable_if_struct_exists() {
1082 cov_mark::check!(test_extract_enum_not_applicable_if_struct_exists);
1083 check_assist_not_applicable(
1084 extract_struct_from_enum_variant,
1085 r#"
1086struct One;
1087enum A { $0One(u8, u32) }
1088"#,
1089 );
1090 }
1091
1092 #[test]
1093 fn test_extract_not_applicable_one_field() {
1094 check_assist_not_applicable(extract_struct_from_enum_variant, r"enum A { $0One(u32) }");
1095 }
1096
1097 #[test]
1098 fn test_extract_not_applicable_no_field_tuple() {
1099 check_assist_not_applicable(extract_struct_from_enum_variant, r"enum A { $0None() }");
1100 }
1101
1102 #[test]
1103 fn test_extract_not_applicable_no_field_named() {
1104 check_assist_not_applicable(extract_struct_from_enum_variant, r"enum A { $0None {} }");
1105 }
1106
1107 #[test]
1108 fn test_extract_struct_only_copies_needed_generics() {
1109 check_assist(
1110 extract_struct_from_enum_variant,
1111 r#"
1112enum X<'a, 'b, 'x> {
1113 $0A { a: &'a &'x mut () },
1114 B { b: &'b () },
1115 C { c: () },
1116}
1117"#,
1118 r#"
1119struct A<'a, 'x> { a: &'a &'x mut () }
1120
1121enum X<'a, 'b, 'x> {
1122 A(A<'a, 'x>),
1123 B { b: &'b () },
1124 C { c: () },
1125}
1126"#,
1127 );
1128 }
1129
1130 #[test]
1131 fn test_extract_struct_with_lifetime_type_const() {
1132 check_assist(
1133 extract_struct_from_enum_variant,
1134 r#"
1135enum X<'b, T, V, const C: usize> {
1136 $0A { a: T, b: X<'b>, c: [u8; C] },
1137 D { d: V },
1138}
1139"#,
1140 r#"
1141struct A<'b, T, const C: usize> { a: T, b: X<'b>, c: [u8; C] }
1142
1143enum X<'b, T, V, const C: usize> {
1144 A(A<'b, T, C>),
1145 D { d: V },
1146}
1147"#,
1148 );
1149 }
1150
1151 #[test]
1152 fn test_extract_struct_without_generics() {
1153 check_assist(
1154 extract_struct_from_enum_variant,
1155 r#"
1156enum X<'a, 'b> {
1157 A { a: &'a () },
1158 B { b: &'b () },
1159 $0C { c: () },
1160}
1161"#,
1162 r#"
1163struct C { c: () }
1164
1165enum X<'a, 'b> {
1166 A { a: &'a () },
1167 B { b: &'b () },
1168 C(C),
1169}
1170"#,
1171 );
1172 }
1173
1174 #[test]
1175 fn test_extract_struct_keeps_trait_bounds() {
1176 check_assist(
1177 extract_struct_from_enum_variant,
1178 r#"
1179enum En<T: TraitT, V: TraitV> {
1180 $0A { a: T },
1181 B { b: V },
1182}
1183"#,
1184 r#"
1185struct A<T: TraitT> { a: T }
1186
1187enum En<T: TraitT, V: TraitV> {
1188 A(A<T>),
1189 B { b: V },
1190}
1191"#,
1192 );
1193 }
1194}