Skip to content

Commit b8191e6

Browse files
authored
Merge pull request #20685 from A4-Tacks/enum-multi-gen-is-as
Add multiple generate for enum generate is, as, try_into
2 parents a60d5fa + 9bf3695 commit b8191e6

File tree

2 files changed

+210
-48
lines changed

2 files changed

+210
-48
lines changed

src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
use std::slice;
2-
31
use ide_db::assists::GroupLabel;
2+
use itertools::Itertools;
43
use stdx::to_lower_snake_case;
54
use syntax::ast::HasVisibility;
65
use syntax::ast::{self, AstNode, HasName};
76

87
use crate::{
98
AssistContext, AssistId, Assists,
10-
utils::{add_method_to_adt, find_struct_impl},
9+
utils::{add_method_to_adt, find_struct_impl, is_selected},
1110
};
1211

1312
// Assist: generate_enum_is_method
@@ -41,20 +40,21 @@ use crate::{
4140
// ```
4241
pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
4342
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
44-
let variant_name = variant.name()?;
4543
let parent_enum = ast::Adt::Enum(variant.parent_enum());
46-
let pattern_suffix = match variant.kind() {
47-
ast::StructKind::Record(_) => " { .. }",
48-
ast::StructKind::Tuple(_) => "(..)",
49-
ast::StructKind::Unit => "",
50-
};
51-
44+
let variants = variant
45+
.parent_enum()
46+
.variant_list()?
47+
.variants()
48+
.filter(|it| is_selected(it, ctx.selection_trimmed(), true))
49+
.collect::<Vec<_>>();
50+
let methods = variants.iter().map(Method::new).collect::<Option<Vec<_>>>()?;
5251
let enum_name = parent_enum.name()?;
5352
let enum_lowercase_name = to_lower_snake_case(&enum_name.to_string()).replace('_', " ");
54-
let fn_name = format!("is_{}", &to_lower_snake_case(&variant_name.text()));
53+
let fn_names = methods.iter().map(|it| it.fn_name.clone()).collect::<Vec<_>>();
54+
stdx::never!(variants.is_empty());
5555

5656
// Return early if we've found an existing new fn
57-
let impl_def = find_struct_impl(ctx, &parent_enum, slice::from_ref(&fn_name))?;
57+
let impl_def = find_struct_impl(ctx, &parent_enum, &fn_names)?;
5858

5959
let target = variant.syntax().text_range();
6060
acc.add_group(
@@ -64,21 +64,47 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_>
6464
target,
6565
|builder| {
6666
let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} "));
67-
let method = format!(
68-
" /// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`].
67+
let method = methods
68+
.iter()
69+
.map(|Method { pattern_suffix, fn_name, variant_name }| {
70+
format!(
71+
" \
72+
/// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`].
6973
///
7074
/// [`{variant_name}`]: {enum_name}::{variant_name}
7175
#[must_use]
7276
{vis}fn {fn_name}(&self) -> bool {{
7377
matches!(self, Self::{variant_name}{pattern_suffix})
7478
}}",
75-
);
79+
)
80+
})
81+
.join("\n\n");
7682

7783
add_method_to_adt(builder, &parent_enum, impl_def, &method);
7884
},
7985
)
8086
}
8187

88+
struct Method {
89+
pattern_suffix: &'static str,
90+
fn_name: String,
91+
variant_name: ast::Name,
92+
}
93+
94+
impl Method {
95+
fn new(variant: &ast::Variant) -> Option<Self> {
96+
let pattern_suffix = match variant.kind() {
97+
ast::StructKind::Record(_) => " { .. }",
98+
ast::StructKind::Tuple(_) => "(..)",
99+
ast::StructKind::Unit => "",
100+
};
101+
102+
let variant_name = variant.name()?;
103+
let fn_name = format!("is_{}", &to_lower_snake_case(&variant_name.text()));
104+
Some(Method { pattern_suffix, fn_name, variant_name })
105+
}
106+
}
107+
82108
#[cfg(test)]
83109
mod tests {
84110
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -113,6 +139,42 @@ impl Variant {
113139
);
114140
}
115141

142+
#[test]
143+
fn test_generate_enum_is_from_multiple_variant() {
144+
check_assist(
145+
generate_enum_is_method,
146+
r#"
147+
enum Variant {
148+
Undefined,
149+
$0Minor,
150+
M$0ajor,
151+
}"#,
152+
r#"enum Variant {
153+
Undefined,
154+
Minor,
155+
Major,
156+
}
157+
158+
impl Variant {
159+
/// Returns `true` if the variant is [`Minor`].
160+
///
161+
/// [`Minor`]: Variant::Minor
162+
#[must_use]
163+
fn is_minor(&self) -> bool {
164+
matches!(self, Self::Minor)
165+
}
166+
167+
/// Returns `true` if the variant is [`Major`].
168+
///
169+
/// [`Major`]: Variant::Major
170+
#[must_use]
171+
fn is_major(&self) -> bool {
172+
matches!(self, Self::Major)
173+
}
174+
}"#,
175+
);
176+
}
177+
116178
#[test]
117179
fn test_generate_enum_is_already_implemented() {
118180
check_assist_not_applicable(

src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs

Lines changed: 133 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::slice;
2-
31
use ide_db::assists::GroupLabel;
42
use itertools::Itertools;
53
use stdx::to_lower_snake_case;
@@ -8,7 +6,7 @@ use syntax::ast::{self, AstNode, HasName};
86

97
use crate::{
108
AssistContext, AssistId, Assists,
11-
utils::{add_method_to_adt, find_struct_impl},
9+
utils::{add_method_to_adt, find_struct_impl, is_selected},
1210
};
1311

1412
// Assist: generate_enum_try_into_method
@@ -128,29 +126,22 @@ fn generate_enum_projection_method(
128126
} = props;
129127

130128
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
131-
let variant_name = variant.name()?;
132129
let parent_enum = ast::Adt::Enum(variant.parent_enum());
133-
134-
let (pattern_suffix, field_type, bound_name) = match variant.kind() {
135-
ast::StructKind::Record(record) => {
136-
let (field,) = record.fields().collect_tuple()?;
137-
let name = field.name()?.to_string();
138-
let ty = field.ty()?;
139-
let pattern_suffix = format!(" {{ {name} }}");
140-
(pattern_suffix, ty, name)
141-
}
142-
ast::StructKind::Tuple(tuple) => {
143-
let (field,) = tuple.fields().collect_tuple()?;
144-
let ty = field.ty()?;
145-
("(v)".to_owned(), ty, "v".to_owned())
146-
}
147-
ast::StructKind::Unit => return None,
148-
};
149-
150-
let fn_name = format!("{fn_name_prefix}_{}", &to_lower_snake_case(&variant_name.text()));
130+
let variants = variant
131+
.parent_enum()
132+
.variant_list()?
133+
.variants()
134+
.filter(|it| is_selected(it, ctx.selection_trimmed(), true))
135+
.collect::<Vec<_>>();
136+
let methods = variants
137+
.iter()
138+
.map(|variant| Method::new(variant, fn_name_prefix))
139+
.collect::<Option<Vec<_>>>()?;
140+
let fn_names = methods.iter().map(|it| it.fn_name.clone()).collect::<Vec<_>>();
141+
stdx::never!(variants.is_empty());
151142

152143
// Return early if we've found an existing new fn
153-
let impl_def = find_struct_impl(ctx, &parent_enum, slice::from_ref(&fn_name))?;
144+
let impl_def = find_struct_impl(ctx, &parent_enum, &fn_names)?;
154145

155146
let target = variant.syntax().text_range();
156147
acc.add_group(
@@ -161,29 +152,66 @@ fn generate_enum_projection_method(
161152
|builder| {
162153
let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} "));
163154

164-
let field_type_syntax = field_type.syntax();
155+
let must_use = if ctx.config.assist_emit_must_use { "#[must_use]\n " } else { "" };
165156

166-
let must_use = if ctx.config.assist_emit_must_use {
167-
"#[must_use]\n "
168-
} else {
169-
""
170-
};
171-
172-
let method = format!(
173-
" {must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type_syntax}{return_suffix} {{
157+
let method = methods
158+
.iter()
159+
.map(|Method { pattern_suffix, field_type, bound_name, fn_name, variant_name }| {
160+
format!(
161+
" \
162+
{must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{
174163
if let Self::{variant_name}{pattern_suffix} = self {{
175164
{happy_case}({bound_name})
176165
}} else {{
177166
{sad_case}
178167
}}
179168
}}"
180-
);
169+
)
170+
})
171+
.join("\n\n");
181172

182173
add_method_to_adt(builder, &parent_enum, impl_def, &method);
183174
},
184175
)
185176
}
186177

178+
struct Method {
179+
pattern_suffix: String,
180+
field_type: ast::Type,
181+
bound_name: String,
182+
fn_name: String,
183+
variant_name: ast::Name,
184+
}
185+
186+
impl Method {
187+
fn new(variant: &ast::Variant, fn_name_prefix: &str) -> Option<Self> {
188+
let variant_name = variant.name()?;
189+
let fn_name = format!("{fn_name_prefix}_{}", &to_lower_snake_case(&variant_name.text()));
190+
191+
match variant.kind() {
192+
ast::StructKind::Record(record) => {
193+
let (field,) = record.fields().collect_tuple()?;
194+
let name = field.name()?.to_string();
195+
let field_type = field.ty()?;
196+
let pattern_suffix = format!(" {{ {name} }}");
197+
Some(Method { pattern_suffix, field_type, bound_name: name, fn_name, variant_name })
198+
}
199+
ast::StructKind::Tuple(tuple) => {
200+
let (field,) = tuple.fields().collect_tuple()?;
201+
let field_type = field.ty()?;
202+
Some(Method {
203+
pattern_suffix: "(v)".to_owned(),
204+
field_type,
205+
bound_name: "v".to_owned(),
206+
variant_name,
207+
fn_name,
208+
})
209+
}
210+
ast::StructKind::Unit => None,
211+
}
212+
}
213+
}
214+
187215
#[cfg(test)]
188216
mod tests {
189217
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -216,6 +244,42 @@ impl Value {
216244
);
217245
}
218246

247+
#[test]
248+
fn test_generate_enum_multiple_try_into_tuple_variant() {
249+
check_assist(
250+
generate_enum_try_into_method,
251+
r#"
252+
enum Value {
253+
Unit(()),
254+
$0Number(i32),
255+
Text(String)$0,
256+
}"#,
257+
r#"enum Value {
258+
Unit(()),
259+
Number(i32),
260+
Text(String),
261+
}
262+
263+
impl Value {
264+
fn try_into_number(self) -> Result<i32, Self> {
265+
if let Self::Number(v) = self {
266+
Ok(v)
267+
} else {
268+
Err(self)
269+
}
270+
}
271+
272+
fn try_into_text(self) -> Result<String, Self> {
273+
if let Self::Text(v) = self {
274+
Ok(v)
275+
} else {
276+
Err(self)
277+
}
278+
}
279+
}"#,
280+
);
281+
}
282+
219283
#[test]
220284
fn test_generate_enum_try_into_already_implemented() {
221285
check_assist_not_applicable(
@@ -323,6 +387,42 @@ impl Value {
323387
);
324388
}
325389

390+
#[test]
391+
fn test_generate_enum_as_multiple_tuple_variant() {
392+
check_assist(
393+
generate_enum_as_method,
394+
r#"
395+
enum Value {
396+
Unit(()),
397+
$0Number(i32),
398+
Text(String)$0,
399+
}"#,
400+
r#"enum Value {
401+
Unit(()),
402+
Number(i32),
403+
Text(String),
404+
}
405+
406+
impl Value {
407+
fn as_number(&self) -> Option<&i32> {
408+
if let Self::Number(v) = self {
409+
Some(v)
410+
} else {
411+
None
412+
}
413+
}
414+
415+
fn as_text(&self) -> Option<&String> {
416+
if let Self::Text(v) = self {
417+
Some(v)
418+
} else {
419+
None
420+
}
421+
}
422+
}"#,
423+
);
424+
}
425+
326426
#[test]
327427
fn test_generate_enum_as_record_variant() {
328428
check_assist(

0 commit comments

Comments
 (0)