Skip to content

Commit f3dc432

Browse files
committed
Account for generics in extract_struct_from_enum_variant
1 parent dbdfeee commit f3dc432

File tree

2 files changed

+48
-20
lines changed

2 files changed

+48
-20
lines changed

crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ use ide_db::{
1111
search::FileReference,
1212
RootDatabase,
1313
};
14+
use itertools::Itertools;
1415
use rustc_hash::FxHashSet;
1516
use syntax::{
1617
algo::find_node_at_offset,
17-
ast::{self, make, AstNode, NameOwner, VisibilityOwner},
18+
ast::{self, make, AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner, VisibilityOwner},
1819
ted, SyntaxNode, T,
1920
};
2021

@@ -100,12 +101,12 @@ pub(crate) fn extract_struct_from_enum_variant(
100101
});
101102
}
102103

103-
let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility());
104+
let def = create_struct_def(variant_name.clone(), &field_list, &enum_ast);
104105
let start_offset = &variant.parent_enum().syntax().clone();
105106
ted::insert_raw(ted::Position::before(start_offset), def.syntax());
106107
ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
107108

108-
update_variant(&variant);
109+
update_variant(&variant, enum_ast.generic_param_list());
109110
},
110111
)
111112
}
@@ -149,7 +150,7 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
149150
fn create_struct_def(
150151
variant_name: ast::Name,
151152
field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
152-
visibility: Option<ast::Visibility>,
153+
enum_: &ast::Enum,
153154
) -> ast::Struct {
154155
let pub_vis = make::visibility_pub();
155156

@@ -184,12 +185,30 @@ fn create_struct_def(
184185
}
185186
};
186187

187-
make::struct_(visibility, variant_name, None, field_list).clone_for_update()
188+
// FIXME: This uses all the generic params of the enum, but the variant might not use all of them.
189+
make::struct_(enum_.visibility(), variant_name, enum_.generic_param_list(), field_list)
190+
.clone_for_update()
188191
}
189192

190-
fn update_variant(variant: &ast::Variant) -> Option<()> {
193+
fn update_variant(variant: &ast::Variant, generic: Option<ast::GenericParamList>) -> Option<()> {
191194
let name = variant.name()?;
192-
let tuple_field = make::tuple_field(None, make::ty(&name.text()));
195+
let ty = match generic {
196+
// FIXME: This uses all the generic params of the enum, but the variant might not use all of them.
197+
Some(gpl) => {
198+
let gpl = gpl.clone_for_update();
199+
gpl.generic_params().for_each(|gp| {
200+
match gp {
201+
ast::GenericParam::LifetimeParam(it) => it.type_bound_list(),
202+
ast::GenericParam::TypeParam(it) => it.type_bound_list(),
203+
ast::GenericParam::ConstParam(_) => return,
204+
}
205+
.map(|it| it.remove());
206+
});
207+
make::ty(&format!("{}<{}>", name.text(), gpl.generic_params().join(", ")))
208+
}
209+
None => make::ty(&name.text()),
210+
};
211+
let tuple_field = make::tuple_field(None, ty);
193212
let replacement = make::variant(
194213
name,
195214
Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
@@ -208,10 +227,9 @@ fn apply_references(
208227
if let Some((scope, path)) = import {
209228
insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg);
210229
}
211-
ted::insert_raw(
212-
ted::Position::before(segment.syntax()),
213-
make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
214-
);
230+
// deep clone to prevent cycle
231+
let path = make::path_from_segments(iter::once(segment.clone_subtree()), false);
232+
ted::insert_raw(ted::Position::before(segment.syntax()), path.clone_for_update().syntax());
215233
ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
216234
ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
217235
}
@@ -278,6 +296,12 @@ mod tests {
278296

279297
use super::*;
280298

299+
fn check_not_applicable(ra_fixture: &str) {
300+
let fixture =
301+
format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
302+
check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
303+
}
304+
281305
#[test]
282306
fn test_extract_struct_several_fields_tuple() {
283307
check_assist(
@@ -311,6 +335,17 @@ enum A { One(One) }"#,
311335
);
312336
}
313337

338+
#[test]
339+
fn test_extract_struct_keeps_generics() {
340+
check_assist(
341+
extract_struct_from_enum_variant,
342+
r"enum En<T> { Var { a: T$0 } }",
343+
r#"struct Var<T>{ pub a: T }
344+
345+
enum En<T> { Var(Var<T>) }"#,
346+
);
347+
}
348+
314349
#[test]
315350
fn test_extract_struct_keep_comments_and_attrs_one_field_named() {
316351
check_assist(
@@ -610,12 +645,6 @@ fn foo() {
610645
);
611646
}
612647

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-
619648
#[test]
620649
fn test_extract_enum_not_applicable_for_element_with_no_fields() {
621650
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)