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