ide_assists/handlers/
extract_struct_from_enum_variant.rs

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
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 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            // record file references of the file the def resides in, we only want to swap to the edited file in the builder once
72            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            // resolve GenericArg in field_list to actual type
114            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            // only check type-namespace
174            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    // for fields without any existing visibility, use visibility of enum
269    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    // take comments from variant
298    ted::insert_all(
299        ted::Position::first_child_of(strukt.syntax()),
300        take_all_comments(variant.syntax()),
301    );
302
303    // copy attributes from enum
304    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    // FIXME: replace with a `ast::make` constructor
323    let ty = match generic_args {
324        Some(generic_args) => make::ty(&format!("{name}{generic_args}")),
325        None => make::ty(&name.text()),
326    };
327
328    // change from a record to a tuple field list
329    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    // remove any ws after the name
334    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
345// Note: this also detaches whitespace after comments,
346// since `SyntaxNode::splice_children` (and by extension `ted::insert_all_raw`)
347// detaches nodes. If we only took the comments, we'd leave behind the old whitespace.
348fn 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    // deep clone to prevent cycle
381    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    // we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
396    // and corresponding nodes up front
397    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    // filter out the reference in marco
432    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}