aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-02-22 20:28:17 +0000
committerGitHub <[email protected]>2021-02-22 20:28:17 +0000
commit27ed1ebf8997cea55fb446ce249b390607b84105 (patch)
treea49a763fee848041fd607f449ad13a0b1040636e /crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
parent8687053b118f47ce1a4962d0baa19b22d40d2758 (diff)
parenteb6cfa7f157690480fca5d55c69dba3fae87ad4f (diff)
Merge #7759
7759: 7526: Rename ide related crates r=Veykril a=chetankhilosiya renamed assists -> ide_assists and ssr -> ide_ssr. the completion crate is already renamed. Co-authored-by: Chetan Khilosiya <[email protected]>
Diffstat (limited to 'crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs')
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs520
1 files changed, 520 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
new file mode 100644
index 000000000..5c7678b53
--- /dev/null
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -0,0 +1,520 @@
1use std::iter;
2
3use either::Either;
4use hir::{AsName, Module, ModuleDef, Name, Variant};
5use ide_db::{
6 defs::Definition,
7 helpers::{
8 insert_use::{insert_use, ImportScope},
9 mod_path_to_ast,
10 },
11 search::FileReference,
12 RootDatabase,
13};
14use rustc_hash::FxHashSet;
15use syntax::{
16 algo::{find_node_at_offset, SyntaxRewriter},
17 ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
18 SourceFile, SyntaxElement, SyntaxNode, T,
19};
20
21use crate::{AssistContext, AssistId, AssistKind, Assists};
22
23// Assist: extract_struct_from_enum_variant
24//
25// Extracts a struct from enum variant.
26//
27// ```
28// enum A { $0One(u32, u32) }
29// ```
30// ->
31// ```
32// struct One(pub u32, pub u32);
33//
34// enum A { One(One) }
35// ```
36pub(crate) fn extract_struct_from_enum_variant(
37 acc: &mut Assists,
38 ctx: &AssistContext,
39) -> Option<()> {
40 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
41 let field_list = extract_field_list_if_applicable(&variant)?;
42
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) {
46 return None;
47 }
48
49 let enum_ast = variant.parent_enum();
50 let enum_hir = ctx.sema.to_def(&enum_ast)?;
51 let target = variant.syntax().text_range();
52 acc.add(
53 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
54 "Extract struct from enum variant",
55 target,
56 |builder| {
57 let variant_hir_name = variant_hir.name(ctx.db());
58 let enum_module_def = ModuleDef::from(enum_hir);
59 let usages =
60 Definition::ModuleDef(ModuleDef::Variant(variant_hir)).usages(&ctx.sema).all();
61
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 {
70 update_reference(
71 ctx,
72 &mut rewriter,
73 reference,
74 &source_file,
75 &enum_module_def,
76 &variant_hir_name,
77 &mut visited_modules_set,
78 );
79 }
80 if file_id == ctx.frange.file_id {
81 def_rewriter = Some(rewriter);
82 continue;
83 }
84 builder.edit_file(file_id);
85 builder.rewrite(rewriter);
86 }
87 let mut rewriter = def_rewriter.unwrap_or_default();
88 update_variant(&mut rewriter, &variant);
89 extract_struct_def(
90 &mut rewriter,
91 &enum_ast,
92 variant_name.clone(),
93 &field_list,
94 &variant.parent_enum().syntax().clone().into(),
95 enum_ast.visibility(),
96 );
97 builder.edit_file(ctx.frange.file_id);
98 builder.rewrite(rewriter);
99 },
100 )
101}
102
103fn 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))
109 }
110 ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
111 Some(Either::Right(field_list))
112 }
113 _ => None,
114 }
115}
116
117fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Variant) -> bool {
118 variant
119 .parent_enum(db)
120 .module(db)
121 .scope(db, None)
122 .into_iter()
123 .filter(|(_, def)| match def {
124 // only check type-namespace
125 hir::ScopeDef::ModuleDef(def) => matches!(
126 def,
127 ModuleDef::Module(_)
128 | ModuleDef::Adt(_)
129 | ModuleDef::Variant(_)
130 | ModuleDef::Trait(_)
131 | ModuleDef::TypeAlias(_)
132 | ModuleDef::BuiltinType(_)
133 ),
134 _ => false,
135 })
136 .any(|(name, _)| name == variant_name.as_name())
137}
138
139fn insert_import(
140 ctx: &AssistContext,
141 rewriter: &mut SyntaxRewriter,
142 scope_node: &SyntaxNode,
143 module: &Module,
144 enum_module_def: &ModuleDef,
145 variant_hir_name: &Name,
146) -> Option<()> {
147 let db = ctx.db();
148 let mod_path = module.find_use_path_prefixed(
149 db,
150 enum_module_def.clone(),
151 ctx.config.insert_use.prefix_kind,
152 );
153 if let Some(mut mod_path) = mod_path {
154 mod_path.pop_segment();
155 mod_path.push_segment(variant_hir_name.clone());
156 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
157 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
158 }
159 Some(())
160}
161
162fn extract_struct_def(
163 rewriter: &mut SyntaxRewriter,
164 enum_: &ast::Enum,
165 variant_name: ast::Name,
166 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
167 start_offset: &SyntaxElement,
168 visibility: Option<ast::Visibility>,
169) -> Option<()> {
170 let pub_vis = Some(make::visibility_pub());
171 let field_list = match field_list {
172 Either::Left(field_list) => {
173 make::record_field_list(field_list.fields().flat_map(|field| {
174 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?))
175 }))
176 .into()
177 }
178 Either::Right(field_list) => make::tuple_field_list(
179 field_list
180 .fields()
181 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))),
182 )
183 .into(),
184 };
185
186 rewriter.insert_before(
187 start_offset,
188 make::struct_(visibility, variant_name, None, field_list).syntax(),
189 );
190 rewriter.insert_before(start_offset, &make::tokens::blank_line());
191
192 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
193 rewriter
194 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
195 }
196 Some(())
197}
198
199fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
200 let name = variant.name()?;
201 let tuple_field = make::tuple_field(None, make::ty(name.text()));
202 let replacement = make::variant(
203 name,
204 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
205 );
206 rewriter.replace(variant.syntax(), replacement.syntax());
207 Some(())
208}
209
210fn update_reference(
211 ctx: &AssistContext,
212 rewriter: &mut SyntaxRewriter,
213 reference: FileReference,
214 source_file: &SourceFile,
215 enum_module_def: &ModuleDef,
216 variant_hir_name: &Name,
217 visited_modules_set: &mut FxHashSet<Module>,
218) -> Option<()> {
219 let offset = reference.range.start();
220 let (segment, expr) = if let Some(path_expr) =
221 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
222 {
223 // tuple variant
224 (path_expr.path()?.segment()?, path_expr.syntax().parent()?)
225 } else if let Some(record_expr) =
226 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
227 {
228 // record variant
229 (record_expr.path()?.segment()?, record_expr.syntax().clone())
230 } else {
231 return None;
232 };
233
234 let module = ctx.sema.scope(&expr).module()?;
235 if !visited_modules_set.contains(&module) {
236 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
237 {
238 visited_modules_set.insert(module);
239 }
240 }
241 rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
242 rewriter.insert_after(segment.syntax(), segment.syntax());
243 rewriter.insert_after(&expr, &make::token(T![')']));
244 Some(())
245}
246
247#[cfg(test)]
248mod tests {
249 use ide_db::helpers::FamousDefs;
250
251 use crate::tests::{check_assist, check_assist_not_applicable};
252
253 use super::*;
254
255 #[test]
256 fn test_extract_struct_several_fields_tuple() {
257 check_assist(
258 extract_struct_from_enum_variant,
259 "enum A { $0One(u32, u32) }",
260 r#"struct One(pub u32, pub u32);
261
262enum A { One(One) }"#,
263 );
264 }
265
266 #[test]
267 fn test_extract_struct_several_fields_named() {
268 check_assist(
269 extract_struct_from_enum_variant,
270 "enum A { $0One { foo: u32, bar: u32 } }",
271 r#"struct One{ pub foo: u32, pub bar: u32 }
272
273enum A { One(One) }"#,
274 );
275 }
276
277 #[test]
278 fn test_extract_struct_one_field_named() {
279 check_assist(
280 extract_struct_from_enum_variant,
281 "enum A { $0One { foo: u32 } }",
282 r#"struct One{ pub foo: u32 }
283
284enum A { One(One) }"#,
285 );
286 }
287
288 #[test]
289 fn test_extract_enum_variant_name_value_namespace() {
290 check_assist(
291 extract_struct_from_enum_variant,
292 r#"const One: () = ();
293enum A { $0One(u32, u32) }"#,
294 r#"const One: () = ();
295struct One(pub u32, pub u32);
296
297enum A { One(One) }"#,
298 );
299 }
300
301 #[test]
302 fn test_extract_struct_pub_visibility() {
303 check_assist(
304 extract_struct_from_enum_variant,
305 "pub enum A { $0One(u32, u32) }",
306 r#"pub struct One(pub u32, pub u32);
307
308pub enum A { One(One) }"#,
309 );
310 }
311
312 #[test]
313 fn test_extract_struct_with_complex_imports() {
314 check_assist(
315 extract_struct_from_enum_variant,
316 r#"mod my_mod {
317 fn another_fn() {
318 let m = my_other_mod::MyEnum::MyField(1, 1);
319 }
320
321 pub mod my_other_mod {
322 fn another_fn() {
323 let m = MyEnum::MyField(1, 1);
324 }
325
326 pub enum MyEnum {
327 $0MyField(u8, u8),
328 }
329 }
330}
331
332fn another_fn() {
333 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
334}"#,
335 r#"use my_mod::my_other_mod::MyField;
336
337mod my_mod {
338 use self::my_other_mod::MyField;
339
340 fn another_fn() {
341 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
342 }
343
344 pub mod my_other_mod {
345 fn another_fn() {
346 let m = MyEnum::MyField(MyField(1, 1));
347 }
348
349 pub struct MyField(pub u8, pub u8);
350
351 pub enum MyEnum {
352 MyField(MyField),
353 }
354 }
355}
356
357fn another_fn() {
358 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
359}"#,
360 );
361 }
362
363 #[test]
364 fn extract_record_fix_references() {
365 check_assist(
366 extract_struct_from_enum_variant,
367 r#"
368enum E {
369 $0V { i: i32, j: i32 }
370}
371
372fn f() {
373 let e = E::V { i: 9, j: 2 };
374}
375"#,
376 r#"
377struct V{ pub i: i32, pub j: i32 }
378
379enum E {
380 V(V)
381}
382
383fn f() {
384 let e = E::V(V { i: 9, j: 2 });
385}
386"#,
387 )
388 }
389
390 #[test]
391 fn test_several_files() {
392 check_assist(
393 extract_struct_from_enum_variant,
394 r#"
395//- /main.rs
396enum E {
397 $0V(i32, i32)
398}
399mod foo;
400
401//- /foo.rs
402use crate::E;
403fn f() {
404 let e = E::V(9, 2);
405}
406"#,
407 r#"
408//- /main.rs
409struct V(pub i32, pub i32);
410
411enum E {
412 V(V)
413}
414mod foo;
415
416//- /foo.rs
417use crate::{E, V};
418fn f() {
419 let e = E::V(V(9, 2));
420}
421"#,
422 )
423 }
424
425 #[test]
426 fn test_several_files_record() {
427 check_assist(
428 extract_struct_from_enum_variant,
429 r#"
430//- /main.rs
431enum E {
432 $0V { i: i32, j: i32 }
433}
434mod foo;
435
436//- /foo.rs
437use crate::E;
438fn f() {
439 let e = E::V { i: 9, j: 2 };
440}
441"#,
442 r#"
443//- /main.rs
444struct V{ pub i: i32, pub j: i32 }
445
446enum E {
447 V(V)
448}
449mod foo;
450
451//- /foo.rs
452use crate::{E, V};
453fn f() {
454 let e = E::V(V { i: 9, j: 2 });
455}
456"#,
457 )
458 }
459
460 #[test]
461 fn test_extract_struct_record_nested_call_exp() {
462 check_assist(
463 extract_struct_from_enum_variant,
464 r#"
465enum A { $0One { a: u32, b: u32 } }
466
467struct B(A);
468
469fn foo() {
470 let _ = B(A::One { a: 1, b: 2 });
471}
472"#,
473 r#"
474struct One{ pub a: u32, pub b: u32 }
475
476enum A { One(One) }
477
478struct B(A);
479
480fn foo() {
481 let _ = B(A::One(One { a: 1, b: 2 }));
482}
483"#,
484 );
485 }
486
487 fn check_not_applicable(ra_fixture: &str) {
488 let fixture =
489 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
490 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
491 }
492
493 #[test]
494 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
495 check_not_applicable("enum A { $0One }");
496 }
497
498 #[test]
499 fn test_extract_enum_not_applicable_if_struct_exists() {
500 check_not_applicable(
501 r#"struct One;
502 enum A { $0One(u8, u32) }"#,
503 );
504 }
505
506 #[test]
507 fn test_extract_not_applicable_one_field() {
508 check_not_applicable(r"enum A { $0One(u32) }");
509 }
510
511 #[test]
512 fn test_extract_not_applicable_no_field_tuple() {
513 check_not_applicable(r"enum A { $0None() }");
514 }
515
516 #[test]
517 fn test_extract_not_applicable_no_field_named() {
518 check_not_applicable(r"enum A { $0None {} }");
519 }
520}