Skip to main content

ide_assists/handlers/
extract_struct_from_enum_variant.rs

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
28// Assist: extract_struct_from_enum_variant
29//
30// Extracts a struct from enum variant.
31//
32// ```
33// enum A { $0One(u32, u32) }
34// ```
35// ->
36// ```
37// struct One(u32, u32);
38//
39// enum A { One(One) }
40// ```
41pub(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            // record file references of the file the def resides in, we only want to swap to the edited file in the builder once
74            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            // resolve GenericArg in field_list to actual type
122            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            // only check type-namespace
197            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    // for fields without any existing visibility, use visibility of enum
293    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    // FIXME: replace with a `ast::make` constructor
343    let ty = match generic_args {
344        Some(generic_args) => make.ty(&format!("{name}{generic_args}")),
345        None => make.ty(&name.text()),
346    };
347
348    // change from a record to a tuple field list
349    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    // remove any ws after the name
354    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    // deep clone to prevent cycle
412    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    // we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
426    // and corresponding nodes up front
427    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    // filter out the reference in marco
460    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}