4 use hir::{Module, ModuleDef, Name, Variant};
8 insert_use::{insert_use, ImportScope},
11 search::FileReference,
14 use rustc_hash::FxHashSet;
16 algo::{find_node_at_offset, SyntaxRewriter},
17 ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
18 SourceFile, SyntaxElement, SyntaxNode, T,
21 use crate::{AssistContext, AssistId, AssistKind, Assists};
23 // Assist: extract_struct_from_enum_variant
25 // Extracts a struct from enum variant.
28 // enum A { $0One(u32, u32) }
32 // struct One(pub u32, pub u32);
34 // enum A { One(One) }
36 pub(crate) fn extract_struct_from_enum_variant(
40 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
41 let field_list = extract_field_list_if_applicable(&variant)?;
43 let variant_name = variant.name()?;
44 let variant_hir = ctx.sema.to_def(&variant)?;
45 if existing_definition(ctx.db(), &variant_name, &variant_hir) {
49 let enum_ast = variant.parent_enum();
50 let enum_hir = ctx.sema.to_def(&enum_ast)?;
51 let target = variant.syntax().text_range();
53 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
54 "Extract struct from enum variant",
57 let variant_hir_name = variant_hir.name(ctx.db());
58 let enum_module_def = ModuleDef::from(enum_hir);
60 Definition::ModuleDef(ModuleDef::Variant(variant_hir)).usages(&ctx.sema).all();
62 let mut visited_modules_set = FxHashSet::default();
63 let current_module = enum_hir.module(ctx.db());
64 visited_modules_set.insert(current_module);
65 let mut def_rewriter = None;
66 for (file_id, references) in usages {
67 let mut rewriter = SyntaxRewriter::default();
68 let source_file = ctx.sema.parse(file_id);
69 for reference in references {
77 &mut visited_modules_set,
80 if file_id == ctx.frange.file_id {
81 def_rewriter = Some(rewriter);
84 builder.edit_file(file_id);
85 builder.rewrite(rewriter);
87 let mut rewriter = def_rewriter.unwrap_or_default();
88 update_variant(&mut rewriter, &variant);
94 &variant.parent_enum().syntax().clone().into(),
95 enum_ast.visibility(),
97 builder.edit_file(ctx.frange.file_id);
98 builder.rewrite(rewriter);
103 fn extract_field_list_if_applicable(
104 variant: &ast::Variant,
105 ) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
106 match variant.kind() {
107 ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
108 Some(Either::Left(field_list))
110 ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
111 Some(Either::Right(field_list))
117 fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Variant) -> bool {
123 .filter(|(_, def)| match def {
124 // only check type-namespace
125 hir::ScopeDef::ModuleDef(def) => matches!(
129 | ModuleDef::Variant(_)
130 | ModuleDef::Trait(_)
131 | ModuleDef::TypeAlias(_)
132 | ModuleDef::BuiltinType(_)
136 .any(|(name, _)| name.to_string() == variant_name.to_string())
141 rewriter: &mut SyntaxRewriter,
142 scope_node: &SyntaxNode,
144 enum_module_def: &ModuleDef,
145 variant_hir_name: &Name,
149 module.find_use_path_prefixed(db, *enum_module_def, ctx.config.insert_use.prefix_kind);
150 if let Some(mut mod_path) = mod_path {
151 mod_path.pop_segment();
152 mod_path.push_segment(variant_hir_name.clone());
153 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
154 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
159 fn extract_struct_def(
160 rewriter: &mut SyntaxRewriter,
162 variant_name: ast::Name,
163 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
164 start_offset: &SyntaxElement,
165 visibility: Option<ast::Visibility>,
167 let pub_vis = Some(make::visibility_pub());
168 let field_list = match field_list {
169 Either::Left(field_list) => {
170 make::record_field_list(field_list.fields().flat_map(|field| {
171 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?))
175 Either::Right(field_list) => make::tuple_field_list(
178 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))),
183 rewriter.insert_before(
185 make::struct_(visibility, variant_name, None, field_list).syntax(),
187 rewriter.insert_before(start_offset, &make::tokens::blank_line());
189 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
191 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
196 fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
197 let name = variant.name()?;
198 let tuple_field = make::tuple_field(None, make::ty(&name.text()));
199 let replacement = make::variant(
201 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
203 rewriter.replace(variant.syntax(), replacement.syntax());
209 rewriter: &mut SyntaxRewriter,
210 reference: FileReference,
211 source_file: &SourceFile,
212 enum_module_def: &ModuleDef,
213 variant_hir_name: &Name,
214 visited_modules_set: &mut FxHashSet<Module>,
216 let offset = reference.range.start();
217 let (segment, expr) = if let Some(path_expr) =
218 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
221 (path_expr.path()?.segment()?, path_expr.syntax().parent()?)
222 } else if let Some(record_expr) =
223 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
226 (record_expr.path()?.segment()?, record_expr.syntax().clone())
231 let module = ctx.sema.scope(&expr).module()?;
232 if !visited_modules_set.contains(&module) {
233 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
235 visited_modules_set.insert(module);
238 rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
239 rewriter.insert_after(segment.syntax(), segment.syntax());
240 rewriter.insert_after(&expr, &make::token(T![')']));
246 use ide_db::helpers::FamousDefs;
248 use crate::tests::{check_assist, check_assist_not_applicable};
253 fn test_extract_struct_several_fields_tuple() {
255 extract_struct_from_enum_variant,
256 "enum A { $0One(u32, u32) }",
257 r#"struct One(pub u32, pub u32);
259 enum A { One(One) }"#,
264 fn test_extract_struct_several_fields_named() {
266 extract_struct_from_enum_variant,
267 "enum A { $0One { foo: u32, bar: u32 } }",
268 r#"struct One{ pub foo: u32, pub bar: u32 }
270 enum A { One(One) }"#,
275 fn test_extract_struct_one_field_named() {
277 extract_struct_from_enum_variant,
278 "enum A { $0One { foo: u32 } }",
279 r#"struct One{ pub foo: u32 }
281 enum A { One(One) }"#,
286 fn test_extract_enum_variant_name_value_namespace() {
288 extract_struct_from_enum_variant,
289 r#"const One: () = ();
290 enum A { $0One(u32, u32) }"#,
291 r#"const One: () = ();
292 struct One(pub u32, pub u32);
294 enum A { One(One) }"#,
299 fn test_extract_struct_pub_visibility() {
301 extract_struct_from_enum_variant,
302 "pub enum A { $0One(u32, u32) }",
303 r#"pub struct One(pub u32, pub u32);
305 pub enum A { One(One) }"#,
310 fn test_extract_struct_with_complex_imports() {
312 extract_struct_from_enum_variant,
315 let m = my_other_mod::MyEnum::MyField(1, 1);
318 pub mod my_other_mod {
320 let m = MyEnum::MyField(1, 1);
330 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
332 r#"use my_mod::my_other_mod::MyField;
335 use self::my_other_mod::MyField;
338 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
341 pub mod my_other_mod {
343 let m = MyEnum::MyField(MyField(1, 1));
346 pub struct MyField(pub u8, pub u8);
355 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
361 fn extract_record_fix_references() {
363 extract_struct_from_enum_variant,
366 $0V { i: i32, j: i32 }
370 let e = E::V { i: 9, j: 2 };
374 struct V{ pub i: i32, pub j: i32 }
381 let e = E::V(V { i: 9, j: 2 });
388 fn test_several_files() {
390 extract_struct_from_enum_variant,
406 struct V(pub i32, pub i32);
416 let e = E::V(V(9, 2));
423 fn test_several_files_record() {
425 extract_struct_from_enum_variant,
429 $0V { i: i32, j: i32 }
436 let e = E::V { i: 9, j: 2 };
441 struct V{ pub i: i32, pub j: i32 }
451 let e = E::V(V { i: 9, j: 2 });
458 fn test_extract_struct_record_nested_call_exp() {
460 extract_struct_from_enum_variant,
462 enum A { $0One { a: u32, b: u32 } }
467 let _ = B(A::One { a: 1, b: 2 });
471 struct One{ pub a: u32, pub b: u32 }
478 let _ = B(A::One(One { a: 1, b: 2 }));
484 fn check_not_applicable(ra_fixture: &str) {
486 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
487 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
491 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
492 check_not_applicable("enum A { $0One }");
496 fn test_extract_enum_not_applicable_if_struct_exists() {
497 check_not_applicable(
499 enum A { $0One(u8, u32) }"#,
504 fn test_extract_not_applicable_one_field() {
505 check_not_applicable(r"enum A { $0One(u32) }");
509 fn test_extract_not_applicable_no_field_tuple() {
510 check_not_applicable(r"enum A { $0None() }");
514 fn test_extract_not_applicable_no_field_named() {
515 check_not_applicable(r"enum A { $0None {} }");