Skip to content

Commit 2dcd5d7

Browse files
committed
add generate_enum_into_method assist
1 parent 4ab285a commit 2dcd5d7

File tree

3 files changed

+275
-20
lines changed

3 files changed

+275
-20
lines changed

crates/ide_assists/src/handlers/generate_enum_match_method.rs

Lines changed: 245 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use itertools::Itertools;
12
use stdx::{format_to, to_lower_snake_case};
23
use syntax::ast::VisibilityOwner;
34
use syntax::ast::{self, AstNode, NameOwner};
@@ -88,14 +89,104 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) ->
8889
)
8990
}
9091

92+
// Assist: generate_enum_into_method
93+
//
94+
// Generate an `into_` method for an enum variant.
95+
//
96+
// ```
97+
// enum Value {
98+
// Number(i32),
99+
// Text(String)$0,
100+
// }
101+
// ```
102+
// ->
103+
// ```
104+
// enum Value {
105+
// Number(i32),
106+
// Text(String),
107+
// }
108+
//
109+
// impl Value {
110+
// fn into_text(self) -> Option<String> {
111+
// if let Self::Text(v) = self {
112+
// Some(v)
113+
// } else {
114+
// None
115+
// }
116+
// }
117+
// }
118+
// ```
119+
pub(crate) fn generate_enum_into_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
120+
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
121+
let variant_name = variant.name()?;
122+
let parent_enum = ast::Adt::Enum(variant.parent_enum());
123+
let variant_kind = variant_kind(&variant);
124+
125+
let fn_name = format!("into_{}", &to_lower_snake_case(variant_name.text()));
126+
127+
// Return early if we've found an existing new fn
128+
let impl_def = find_struct_impl(
129+
&ctx,
130+
&parent_enum,
131+
&fn_name,
132+
)?;
133+
134+
let field_type = variant_kind.single_field_type()?;
135+
let (pattern_suffix, bound_name) = variant_kind.binding_pattern()?;
136+
137+
let target = variant.syntax().text_range();
138+
acc.add(
139+
AssistId("generate_enum_into_method", AssistKind::Generate),
140+
"Generate an `into_` method for an enum variant",
141+
target,
142+
|builder| {
143+
let mut buf = String::with_capacity(512);
144+
145+
if impl_def.is_some() {
146+
buf.push('\n');
147+
}
148+
149+
let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
150+
format_to!(
151+
buf,
152+
" {}fn {}(self) -> Option<{}> {{
153+
if let Self::{}{} = self {{
154+
Some({})
155+
}} else {{
156+
None
157+
}}
158+
}}",
159+
vis,
160+
fn_name,
161+
field_type.syntax(),
162+
variant_name,
163+
pattern_suffix,
164+
bound_name,
165+
);
166+
167+
let start_offset = impl_def
168+
.and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
169+
.unwrap_or_else(|| {
170+
buf = generate_impl_text(&parent_enum, &buf);
171+
parent_enum.syntax().text_range().end()
172+
});
173+
174+
builder.insert(start_offset, buf);
175+
},
176+
)
177+
}
178+
91179
enum VariantKind {
92180
Unit,
93181
/// Tuple with a single field
94-
NewtypeTuple,
182+
NewtypeTuple { ty: Option<ast::Type> },
95183
/// Tuple with 0 or more than 2 fields
96184
Tuple,
97185
/// Record with a single field
98-
NewtypeRecord { field_name: Option<ast::Name> },
186+
NewtypeRecord {
187+
field_name: Option<ast::Name>,
188+
field_type: Option<ast::Type>,
189+
},
99190
/// Record with 0 or more than 2 fields
100191
Record,
101192
}
@@ -104,27 +195,57 @@ impl VariantKind {
104195
fn pattern_suffix(&self) -> &'static str {
105196
match self {
106197
VariantKind::Unit => "",
107-
VariantKind::NewtypeTuple |
198+
VariantKind::NewtypeTuple { .. } |
108199
VariantKind::Tuple => "(..)",
109200
VariantKind::NewtypeRecord { .. } |
110201
VariantKind::Record => " { .. }",
111202
}
112203
}
204+
205+
fn binding_pattern(&self) -> Option<(String, String)> {
206+
match self {
207+
VariantKind::Unit |
208+
VariantKind::Tuple |
209+
VariantKind::Record |
210+
VariantKind::NewtypeRecord { field_name: None, .. } => None,
211+
VariantKind::NewtypeTuple { .. } => {
212+
Some(("(v)".to_owned(), "v".to_owned()))
213+
}
214+
VariantKind::NewtypeRecord { field_name: Some(name), .. } => {
215+
Some((
216+
format!(" {{ {} }}", name.syntax()),
217+
name.syntax().to_string(),
218+
))
219+
}
220+
}
221+
}
222+
223+
fn single_field_type(&self) -> Option<&ast::Type> {
224+
match self {
225+
VariantKind::Unit |
226+
VariantKind::Tuple |
227+
VariantKind::Record => None,
228+
VariantKind::NewtypeTuple { ty } => ty.as_ref(),
229+
VariantKind::NewtypeRecord { field_type, .. } => field_type.as_ref(),
230+
}
231+
}
113232
}
114233

115234
fn variant_kind(variant: &ast::Variant) -> VariantKind {
116235
match variant.kind() {
117236
ast::StructKind::Record(record) => {
118-
if record.fields().count() == 1 {
119-
let field_name = record.fields().nth(0).unwrap().name();
120-
VariantKind::NewtypeRecord { field_name }
237+
if let Some((single_field,)) = record.fields().collect_tuple() {
238+
let field_name = single_field.name();
239+
let field_type = single_field.ty();
240+
VariantKind::NewtypeRecord { field_name, field_type }
121241
} else {
122242
VariantKind::Record
123243
}
124244
}
125245
ast::StructKind::Tuple(tuple) => {
126-
if tuple.fields().count() == 1 {
127-
VariantKind::NewtypeTuple
246+
if let Some((single_field,)) = tuple.fields().collect_tuple() {
247+
let ty = single_field.ty();
248+
VariantKind::NewtypeTuple { ty }
128249
} else {
129250
VariantKind::Tuple
130251
}
@@ -139,12 +260,8 @@ mod tests {
139260

140261
use super::*;
141262

142-
fn check_not_applicable(ra_fixture: &str) {
143-
check_assist_not_applicable(generate_enum_is_method, ra_fixture)
144-
}
145-
146263
#[test]
147-
fn test_generate_enum_match_from_variant() {
264+
fn test_generate_enum_is_from_variant() {
148265
check_assist(
149266
generate_enum_is_method,
150267
r#"
@@ -169,8 +286,9 @@ impl Variant {
169286
}
170287

171288
#[test]
172-
fn test_generate_enum_match_already_implemented() {
173-
check_not_applicable(
289+
fn test_generate_enum_is_already_implemented() {
290+
check_assist_not_applicable(
291+
generate_enum_is_method,
174292
r#"
175293
enum Variant {
176294
Undefined,
@@ -187,7 +305,7 @@ impl Variant {
187305
}
188306

189307
#[test]
190-
fn test_generate_enum_match_from_tuple_variant() {
308+
fn test_generate_enum_is_from_tuple_variant() {
191309
check_assist(
192310
generate_enum_is_method,
193311
r#"
@@ -212,7 +330,7 @@ impl Variant {
212330
}
213331

214332
#[test]
215-
fn test_generate_enum_match_from_record_variant() {
333+
fn test_generate_enum_is_from_record_variant() {
216334
check_assist(
217335
generate_enum_is_method,
218336
r#"
@@ -237,7 +355,7 @@ impl Variant {
237355
}
238356

239357
#[test]
240-
fn test_generate_enum_match_from_variant_with_one_variant() {
358+
fn test_generate_enum_is_from_variant_with_one_variant() {
241359
check_assist(
242360
generate_enum_is_method,
243361
r#"enum Variant { Undefi$0ned }"#,
@@ -254,7 +372,7 @@ impl Variant {
254372
}
255373

256374
#[test]
257-
fn test_generate_enum_match_from_variant_with_visibility_marker() {
375+
fn test_generate_enum_is_from_variant_with_visibility_marker() {
258376
check_assist(
259377
generate_enum_is_method,
260378
r#"
@@ -279,7 +397,7 @@ impl Variant {
279397
}
280398

281399
#[test]
282-
fn test_multiple_generate_enum_match_from_variant() {
400+
fn test_multiple_generate_enum_is_from_variant() {
283401
check_assist(
284402
generate_enum_is_method,
285403
r#"
@@ -311,6 +429,113 @@ impl Variant {
311429
fn is_major(&self) -> bool {
312430
matches!(self, Self::Major)
313431
}
432+
}"#,
433+
);
434+
}
435+
436+
#[test]
437+
fn test_generate_enum_into_tuple_variant() {
438+
check_assist(
439+
generate_enum_into_method,
440+
r#"
441+
enum Value {
442+
Number(i32),
443+
Text(String)$0,
444+
}"#,
445+
r#"enum Value {
446+
Number(i32),
447+
Text(String),
448+
}
449+
450+
impl Value {
451+
fn into_text(self) -> Option<String> {
452+
if let Self::Text(v) = self {
453+
Some(v)
454+
} else {
455+
None
456+
}
457+
}
458+
}"#,
459+
);
460+
}
461+
462+
#[test]
463+
fn test_generate_enum_into_already_implemented() {
464+
check_assist_not_applicable(
465+
generate_enum_into_method,
466+
r#"enum Value {
467+
Number(i32),
468+
Text(String)$0,
469+
}
470+
471+
impl Value {
472+
fn into_text(self) -> Option<String> {
473+
if let Self::Text(v) = self {
474+
Some(v)
475+
} else {
476+
None
477+
}
478+
}
479+
}"#,
480+
);
481+
}
482+
483+
#[test]
484+
fn test_generate_enum_into_unit_variant() {
485+
check_assist_not_applicable(
486+
generate_enum_into_method,
487+
r#"enum Value {
488+
Number(i32),
489+
Text(String),
490+
Unit$0,
491+
}"#,
492+
);
493+
}
494+
495+
#[test]
496+
fn test_generate_enum_into_record_with_multiple_fields() {
497+
check_assist_not_applicable(
498+
generate_enum_into_method,
499+
r#"enum Value {
500+
Number(i32),
501+
Text(String),
502+
Both { first: i32, second: String }$0,
503+
}"#,
504+
);
505+
}
506+
507+
#[test]
508+
fn test_generate_enum_into_tuple_with_multiple_fields() {
509+
check_assist_not_applicable(
510+
generate_enum_into_method,
511+
r#"enum Value {
512+
Number(i32),
513+
Text(String, String)$0,
514+
}"#,
515+
);
516+
}
517+
518+
#[test]
519+
fn test_generate_enum_into_record_variant() {
520+
check_assist(
521+
generate_enum_into_method,
522+
r#"enum Value {
523+
Number(i32),
524+
Text { text: String }$0,
525+
}"#,
526+
r#"enum Value {
527+
Number(i32),
528+
Text { text: String },
529+
}
530+
531+
impl Value {
532+
fn into_text(self) -> Option<String> {
533+
if let Self::Text { text } = self {
534+
Some(text)
535+
} else {
536+
None
537+
}
538+
}
314539
}"#,
315540
);
316541
}

crates/ide_assists/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ mod handlers {
190190
generate_default_from_enum_variant::generate_default_from_enum_variant,
191191
generate_derive::generate_derive,
192192
generate_enum_match_method::generate_enum_is_method,
193+
generate_enum_match_method::generate_enum_into_method,
193194
generate_from_impl_for_enum::generate_from_impl_for_enum,
194195
generate_function::generate_function,
195196
generate_getter::generate_getter,

0 commit comments

Comments
 (0)