Skip to content

Commit a421482

Browse files
bors[bot]Veykril
andauthored
Merge #9112
9112: Fix some bugs in `extract_struct_from_enum_variant` assist r=Veykril a=Veykril bors r+ Fixes #9100 Fixes #9099 Kind of fixes #9109, it now copies all the generics might be incorrect if the variant doesn't use all of them) Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
2 parents 2022cfc + 6ffe1d9 commit a421482

File tree

2 files changed

+127
-47
lines changed

2 files changed

+127
-47
lines changed

crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs

Lines changed: 125 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,19 @@ use ide_db::{
1111
search::FileReference,
1212
RootDatabase,
1313
};
14+
use itertools::Itertools;
1415
use rustc_hash::FxHashSet;
1516
use syntax::{
16-
algo::find_node_at_offset,
17-
ast::{self, make, AstNode, NameOwner, VisibilityOwner},
18-
ted, SyntaxNode, T,
17+
ast::{
18+
self, make, AstNode, AttrsOwner, GenericParamsOwner, NameOwner, TypeBoundsOwner,
19+
VisibilityOwner,
20+
},
21+
match_ast,
22+
ted::{self, Position},
23+
SyntaxNode, T,
1924
};
2025

21-
use crate::{AssistContext, AssistId, AssistKind, Assists};
26+
use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
2227

2328
// Assist: extract_struct_from_enum_variant
2429
//
@@ -70,11 +75,10 @@ pub(crate) fn extract_struct_from_enum_variant(
7075
continue;
7176
}
7277
builder.edit_file(file_id);
73-
let source_file = builder.make_mut(ctx.sema.parse(file_id));
7478
let processed = process_references(
7579
ctx,
80+
builder,
7681
&mut visited_modules_set,
77-
source_file.syntax(),
7882
&enum_module_def,
7983
&variant_hir_name,
8084
references,
@@ -84,13 +88,12 @@ pub(crate) fn extract_struct_from_enum_variant(
8488
});
8589
}
8690
builder.edit_file(ctx.frange.file_id);
87-
let source_file = builder.make_mut(ctx.sema.parse(ctx.frange.file_id));
8891
let variant = builder.make_mut(variant.clone());
8992
if let Some(references) = def_file_references {
9093
let processed = process_references(
9194
ctx,
95+
builder,
9296
&mut visited_modules_set,
93-
source_file.syntax(),
9497
&enum_module_def,
9598
&variant_hir_name,
9699
references,
@@ -100,12 +103,12 @@ pub(crate) fn extract_struct_from_enum_variant(
100103
});
101104
}
102105

103-
let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility());
106+
let def = create_struct_def(variant_name.clone(), &field_list, &enum_ast);
104107
let start_offset = &variant.parent_enum().syntax().clone();
105108
ted::insert_raw(ted::Position::before(start_offset), def.syntax());
106109
ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
107110

108-
update_variant(&variant);
111+
update_variant(&variant, enum_ast.generic_param_list());
109112
},
110113
)
111114
}
@@ -149,7 +152,7 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
149152
fn create_struct_def(
150153
variant_name: ast::Name,
151154
field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
152-
visibility: Option<ast::Visibility>,
155+
enum_: &ast::Enum,
153156
) -> ast::Struct {
154157
let pub_vis = make::visibility_pub();
155158

@@ -184,12 +187,38 @@ fn create_struct_def(
184187
}
185188
};
186189

187-
make::struct_(visibility, variant_name, None, field_list).clone_for_update()
190+
// FIXME: This uses all the generic params of the enum, but the variant might not use all of them.
191+
let strukt =
192+
make::struct_(enum_.visibility(), variant_name, enum_.generic_param_list(), field_list)
193+
.clone_for_update();
194+
195+
// copy attributes
196+
ted::insert_all(
197+
Position::first_child_of(strukt.syntax()),
198+
enum_.attrs().map(|it| it.syntax().clone_for_update().into()).collect(),
199+
);
200+
strukt
188201
}
189202

190-
fn update_variant(variant: &ast::Variant) -> Option<()> {
203+
fn update_variant(variant: &ast::Variant, generic: Option<ast::GenericParamList>) -> Option<()> {
191204
let name = variant.name()?;
192-
let tuple_field = make::tuple_field(None, make::ty(&name.text()));
205+
let ty = match generic {
206+
// FIXME: This uses all the generic params of the enum, but the variant might not use all of them.
207+
Some(gpl) => {
208+
let gpl = gpl.clone_for_update();
209+
gpl.generic_params().for_each(|gp| {
210+
match gp {
211+
ast::GenericParam::LifetimeParam(it) => it.type_bound_list(),
212+
ast::GenericParam::TypeParam(it) => it.type_bound_list(),
213+
ast::GenericParam::ConstParam(_) => return,
214+
}
215+
.map(|it| it.remove());
216+
});
217+
make::ty(&format!("{}<{}>", name.text(), gpl.generic_params().join(", ")))
218+
}
219+
None => make::ty(&name.text()),
220+
};
221+
let tuple_field = make::tuple_field(None, ty);
193222
let replacement = make::variant(
194223
name,
195224
Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
@@ -208,18 +237,17 @@ fn apply_references(
208237
if let Some((scope, path)) = import {
209238
insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg);
210239
}
211-
ted::insert_raw(
212-
ted::Position::before(segment.syntax()),
213-
make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
214-
);
240+
// deep clone to prevent cycle
241+
let path = make::path_from_segments(iter::once(segment.clone_subtree()), false);
242+
ted::insert_raw(ted::Position::before(segment.syntax()), path.clone_for_update().syntax());
215243
ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
216244
ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
217245
}
218246

219247
fn process_references(
220248
ctx: &AssistContext,
249+
builder: &mut AssistBuilder,
221250
visited_modules: &mut FxHashSet<Module>,
222-
source_file: &SyntaxNode,
223251
enum_module_def: &ModuleDef,
224252
variant_hir_name: &Name,
225253
refs: Vec<FileReference>,
@@ -228,8 +256,9 @@ fn process_references(
228256
// and corresponding nodes up front
229257
refs.into_iter()
230258
.flat_map(|reference| {
231-
let (segment, scope_node, module) =
232-
reference_to_node(&ctx.sema, source_file, reference)?;
259+
let (segment, scope_node, module) = reference_to_node(&ctx.sema, reference)?;
260+
let segment = builder.make_mut(segment);
261+
let scope_node = builder.make_syntax_mut(scope_node);
233262
if !visited_modules.contains(&module) {
234263
let mod_path = module.find_use_path_prefixed(
235264
ctx.sema.db,
@@ -251,23 +280,22 @@ fn process_references(
251280

252281
fn reference_to_node(
253282
sema: &hir::Semantics<RootDatabase>,
254-
source_file: &SyntaxNode,
255283
reference: FileReference,
256284
) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
257-
let offset = reference.range.start();
258-
if let Some(path_expr) = find_node_at_offset::<ast::PathExpr>(source_file, offset) {
259-
// tuple variant
260-
Some((path_expr.path()?.segment()?, path_expr.syntax().parent()?))
261-
} else if let Some(record_expr) = find_node_at_offset::<ast::RecordExpr>(source_file, offset) {
262-
// record variant
263-
Some((record_expr.path()?.segment()?, record_expr.syntax().clone()))
264-
} else {
265-
None
266-
}
267-
.and_then(|(segment, expr)| {
268-
let module = sema.scope(&expr).module()?;
269-
Some((segment, expr, module))
270-
})
285+
let segment =
286+
reference.name.as_name_ref()?.syntax().parent().and_then(ast::PathSegment::cast)?;
287+
let parent = segment.parent_path().syntax().parent()?;
288+
let expr_or_pat = match_ast! {
289+
match parent {
290+
ast::PathExpr(_it) => parent.parent()?,
291+
ast::RecordExpr(_it) => parent,
292+
ast::TupleStructPat(_it) => parent,
293+
ast::RecordPat(_it) => parent,
294+
_ => return None,
295+
}
296+
};
297+
let module = sema.scope(&expr_or_pat).module()?;
298+
Some((segment, expr_or_pat, module))
271299
}
272300

273301
#[cfg(test)]
@@ -278,6 +306,12 @@ mod tests {
278306

279307
use super::*;
280308

309+
fn check_not_applicable(ra_fixture: &str) {
310+
let fixture =
311+
format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
312+
check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
313+
}
314+
281315
#[test]
282316
fn test_extract_struct_several_fields_tuple() {
283317
check_assist(
@@ -311,6 +345,32 @@ enum A { One(One) }"#,
311345
);
312346
}
313347

348+
#[test]
349+
fn test_extract_struct_carries_over_generics() {
350+
check_assist(
351+
extract_struct_from_enum_variant,
352+
r"enum En<T> { Var { a: T$0 } }",
353+
r#"struct Var<T>{ pub a: T }
354+
355+
enum En<T> { Var(Var<T>) }"#,
356+
);
357+
}
358+
359+
#[test]
360+
fn test_extract_struct_carries_over_attributes() {
361+
check_assist(
362+
extract_struct_from_enum_variant,
363+
r#"#[derive(Debug)]
364+
#[derive(Clone)]
365+
enum Enum { Variant{ field: u32$0 } }"#,
366+
r#"#[derive(Debug)]#[derive(Clone)] struct Variant{ pub field: u32 }
367+
368+
#[derive(Debug)]
369+
#[derive(Clone)]
370+
enum Enum { Variant(Variant) }"#,
371+
);
372+
}
373+
314374
#[test]
315375
fn test_extract_struct_keep_comments_and_attrs_one_field_named() {
316376
check_assist(
@@ -496,7 +556,7 @@ enum E {
496556
}
497557
498558
fn f() {
499-
let e = E::V { i: 9, j: 2 };
559+
let E::V { i, j } = E::V { i: 9, j: 2 };
500560
}
501561
"#,
502562
r#"
@@ -507,7 +567,34 @@ enum E {
507567
}
508568
509569
fn f() {
510-
let e = E::V(V { i: 9, j: 2 });
570+
let E::V(V { i, j }) = E::V(V { i: 9, j: 2 });
571+
}
572+
"#,
573+
)
574+
}
575+
576+
#[test]
577+
fn extract_record_fix_references2() {
578+
check_assist(
579+
extract_struct_from_enum_variant,
580+
r#"
581+
enum E {
582+
$0V(i32, i32)
583+
}
584+
585+
fn f() {
586+
let E::V(i, j) = E::V(9, 2);
587+
}
588+
"#,
589+
r#"
590+
struct V(pub i32, pub i32);
591+
592+
enum E {
593+
V(V)
594+
}
595+
596+
fn f() {
597+
let E::V(V(i, j)) = E::V(V(9, 2));
511598
}
512599
"#,
513600
)
@@ -610,12 +697,6 @@ fn foo() {
610697
);
611698
}
612699

613-
fn check_not_applicable(ra_fixture: &str) {
614-
let fixture =
615-
format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
616-
check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
617-
}
618-
619700
#[test]
620701
fn test_extract_enum_not_applicable_for_element_with_no_fields() {
621702
check_not_applicable("enum A { $0One }");

crates/syntax/src/ast/make.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -580,12 +580,11 @@ pub fn fn_(
580580
pub fn struct_(
581581
visibility: Option<ast::Visibility>,
582582
strukt_name: ast::Name,
583-
type_params: Option<ast::GenericParamList>,
583+
generic_param_list: Option<ast::GenericParamList>,
584584
field_list: ast::FieldList,
585585
) -> ast::Struct {
586586
let semicolon = if matches!(field_list, ast::FieldList::TupleFieldList(_)) { ";" } else { "" };
587-
let type_params =
588-
if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
587+
let type_params = generic_param_list.map_or_else(String::new, |it| it.to_string());
589588
let visibility = match visibility {
590589
None => String::new(),
591590
Some(it) => format!("{} ", it),

0 commit comments

Comments
 (0)