Skip to content

Commit fc3daf6

Browse files
authored
Merge pull request #21038 from A4-Tacks/gen-multi-from-impl-enum
Support multiple variant for generate_from_impl_for_enum
2 parents 8ea3200 + 359b771 commit fc3daf6

File tree

2 files changed

+103
-30
lines changed

2 files changed

+103
-30
lines changed

crates/ide-assists/src/handlers/generate_from_impl_for_enum.rs

Lines changed: 94 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ use hir::next_solver::{DbInterner, TypingMode};
22
use ide_db::{RootDatabase, famous_defs::FamousDefs};
33
use syntax::ast::{self, AstNode, HasName};
44

5-
use crate::{AssistContext, AssistId, Assists, utils::generate_trait_impl_text_intransitive};
5+
use crate::{
6+
AssistContext, AssistId, Assists,
7+
utils::{generate_trait_impl_text_intransitive, is_selected},
8+
};
69

710
// Assist: generate_from_impl_for_enum
811
//
@@ -26,8 +29,68 @@ pub(crate) fn generate_from_impl_for_enum(
2629
ctx: &AssistContext<'_>,
2730
) -> Option<()> {
2831
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
29-
let variant_name = variant.name()?;
30-
let enum_ = ast::Adt::Enum(variant.parent_enum());
32+
let adt = ast::Adt::Enum(variant.parent_enum());
33+
let variants = selected_variants(ctx, &variant)?;
34+
35+
let target = variant.syntax().text_range();
36+
acc.add(
37+
AssistId::generate("generate_from_impl_for_enum"),
38+
"Generate `From` impl for this enum variant(s)",
39+
target,
40+
|edit| {
41+
let start_offset = variant.parent_enum().syntax().text_range().end();
42+
let from_impl = variants
43+
.into_iter()
44+
.map(|variant_info| {
45+
let from_trait = format!("From<{}>", variant_info.ty);
46+
let impl_code = generate_impl_code(variant_info);
47+
generate_trait_impl_text_intransitive(&adt, &from_trait, &impl_code)
48+
})
49+
.collect::<String>();
50+
edit.insert(start_offset, from_impl);
51+
},
52+
)
53+
}
54+
55+
fn generate_impl_code(VariantInfo { name, field_name, ty }: VariantInfo) -> String {
56+
if let Some(field) = field_name {
57+
format!(
58+
r#" fn from({field}: {ty}) -> Self {{
59+
Self::{name} {{ {field} }}
60+
}}"#
61+
)
62+
} else {
63+
format!(
64+
r#" fn from(v: {ty}) -> Self {{
65+
Self::{name}(v)
66+
}}"#
67+
)
68+
}
69+
}
70+
71+
struct VariantInfo {
72+
name: ast::Name,
73+
field_name: Option<ast::Name>,
74+
ty: ast::Type,
75+
}
76+
77+
fn selected_variants(ctx: &AssistContext<'_>, variant: &ast::Variant) -> Option<Vec<VariantInfo>> {
78+
variant
79+
.parent_enum()
80+
.variant_list()?
81+
.variants()
82+
.filter(|it| is_selected(it, ctx.selection_trimmed(), true))
83+
.map(|variant| {
84+
let (name, ty) = extract_variant_info(&ctx.sema, &variant)?;
85+
Some(VariantInfo { name: variant.name()?, field_name: name, ty })
86+
})
87+
.collect()
88+
}
89+
90+
fn extract_variant_info(
91+
sema: &'_ hir::Semantics<'_, RootDatabase>,
92+
variant: &ast::Variant,
93+
) -> Option<(Option<ast::Name>, ast::Type)> {
3194
let (field_name, field_type) = match variant.kind() {
3295
ast::StructKind::Tuple(field_list) => {
3396
if field_list.fields().count() != 1 {
@@ -45,36 +108,11 @@ pub(crate) fn generate_from_impl_for_enum(
45108
ast::StructKind::Unit => return None,
46109
};
47110

48-
if existing_from_impl(&ctx.sema, &variant).is_some() {
111+
if existing_from_impl(sema, variant).is_some() {
49112
cov_mark::hit!(test_add_from_impl_already_exists);
50113
return None;
51114
}
52-
53-
let target = variant.syntax().text_range();
54-
acc.add(
55-
AssistId::generate("generate_from_impl_for_enum"),
56-
"Generate `From` impl for this enum variant",
57-
target,
58-
|edit| {
59-
let start_offset = variant.parent_enum().syntax().text_range().end();
60-
let from_trait = format!("From<{field_type}>");
61-
let impl_code = if let Some(name) = field_name {
62-
format!(
63-
r#" fn from({name}: {field_type}) -> Self {{
64-
Self::{variant_name} {{ {name} }}
65-
}}"#
66-
)
67-
} else {
68-
format!(
69-
r#" fn from(v: {field_type}) -> Self {{
70-
Self::{variant_name}(v)
71-
}}"#
72-
)
73-
};
74-
let from_impl = generate_trait_impl_text_intransitive(&enum_, &from_trait, &impl_code);
75-
edit.insert(start_offset, from_impl);
76-
},
77-
)
115+
Some((field_name, field_type))
78116
}
79117

80118
fn existing_from_impl(
@@ -123,6 +161,32 @@ impl From<u32> for A {
123161
);
124162
}
125163

164+
#[test]
165+
fn test_generate_from_impl_for_multiple_enum_variants() {
166+
check_assist(
167+
generate_from_impl_for_enum,
168+
r#"
169+
//- minicore: from
170+
enum A { $0Foo(u32), Bar$0(i32) }
171+
"#,
172+
r#"
173+
enum A { Foo(u32), Bar(i32) }
174+
175+
impl From<u32> for A {
176+
fn from(v: u32) -> Self {
177+
Self::Foo(v)
178+
}
179+
}
180+
181+
impl From<i32> for A {
182+
fn from(v: i32) -> Self {
183+
Self::Bar(v)
184+
}
185+
}
186+
"#,
187+
);
188+
}
189+
126190
// FIXME(next-solver): it would be nice to not be *required* to resolve the
127191
// path in order to properly generate assists
128192
#[test]

crates/ide-assists/src/utils.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,15 @@ pub(crate) fn cover_let_chain(mut expr: ast::Expr, range: TextRange) -> Option<a
11631163
}
11641164
}
11651165

1166+
pub(crate) fn is_selected(
1167+
it: &impl AstNode,
1168+
selection: syntax::TextRange,
1169+
allow_empty: bool,
1170+
) -> bool {
1171+
selection.intersect(it.syntax().text_range()).is_some_and(|it| !it.is_empty())
1172+
|| allow_empty && it.syntax().text_range().contains_range(selection)
1173+
}
1174+
11661175
pub fn is_body_const(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> bool {
11671176
let mut is_const = true;
11681177
preorder_expr(expr, &mut |ev| {

0 commit comments

Comments
 (0)