aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/generate_enum_match_method.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/generate_enum_match_method.rs')
-rw-r--r--crates/ide_assists/src/handlers/generate_enum_match_method.rs406
1 files changed, 7 insertions, 399 deletions
diff --git a/crates/ide_assists/src/handlers/generate_enum_match_method.rs b/crates/ide_assists/src/handlers/generate_enum_match_method.rs
index 670c82200..7e181a480 100644
--- a/crates/ide_assists/src/handlers/generate_enum_match_method.rs
+++ b/crates/ide_assists/src/handlers/generate_enum_match_method.rs
@@ -1,11 +1,9 @@
1use itertools::Itertools;
2use stdx::to_lower_snake_case; 1use stdx::to_lower_snake_case;
3use syntax::ast::VisibilityOwner; 2use syntax::ast::VisibilityOwner;
4use syntax::ast::{self, AstNode, NameOwner}; 3use syntax::ast::{self, AstNode, NameOwner};
5 4
6use crate::{ 5use crate::{
7 assist_context::AssistBuilder, 6 utils::{add_method_to_adt, find_struct_impl},
8 utils::{find_impl_block_end, find_struct_impl, generate_impl_text},
9 AssistContext, AssistId, AssistKind, Assists, 7 AssistContext, AssistId, AssistKind, Assists,
10}; 8};
11 9
@@ -39,7 +37,11 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) ->
39 let variant = ctx.find_node_at_offset::<ast::Variant>()?; 37 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
40 let variant_name = variant.name()?; 38 let variant_name = variant.name()?;
41 let parent_enum = ast::Adt::Enum(variant.parent_enum()); 39 let parent_enum = ast::Adt::Enum(variant.parent_enum());
42 let variant_kind = variant_kind(&variant); 40 let pattern_suffix = match variant.kind() {
41 ast::StructKind::Record(_) => " { .. }",
42 ast::StructKind::Tuple(_) => "(..)",
43 ast::StructKind::Unit => "",
44 };
43 45
44 let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); 46 let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string());
45 let fn_name = format!("is_{}", &to_lower_snake_case(variant_name.text())); 47 let fn_name = format!("is_{}", &to_lower_snake_case(variant_name.text()));
@@ -59,12 +61,7 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) ->
59 {}fn {}(&self) -> bool {{ 61 {}fn {}(&self) -> bool {{
60 matches!(self, Self::{}{}) 62 matches!(self, Self::{}{})
61 }}", 63 }}",
62 enum_lowercase_name, 64 enum_lowercase_name, variant_name, vis, fn_name, variant_name, pattern_suffix,
63 variant_name,
64 vis,
65 fn_name,
66 variant_name,
67 variant_kind.pattern_suffix(),
68 ); 65 );
69 66
70 add_method_to_adt(builder, &parent_enum, impl_def, &method); 67 add_method_to_adt(builder, &parent_enum, impl_def, &method);
@@ -72,237 +69,6 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) ->
72 ) 69 )
73} 70}
74 71
75// Assist: generate_enum_into_method
76//
77// Generate an `into_` method for an enum variant.
78//
79// ```
80// enum Value {
81// Number(i32),
82// Text(String)$0,
83// }
84// ```
85// ->
86// ```
87// enum Value {
88// Number(i32),
89// Text(String),
90// }
91//
92// impl Value {
93// fn into_text(self) -> Option<String> {
94// if let Self::Text(v) = self {
95// Some(v)
96// } else {
97// None
98// }
99// }
100// }
101// ```
102pub(crate) fn generate_enum_into_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
103 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
104 let variant_name = variant.name()?;
105 let parent_enum = ast::Adt::Enum(variant.parent_enum());
106 let variant_kind = variant_kind(&variant);
107
108 let fn_name = format!("into_{}", &to_lower_snake_case(variant_name.text()));
109
110 // Return early if we've found an existing new fn
111 let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?;
112
113 let field_type = variant_kind.single_field_type()?;
114 let (pattern_suffix, bound_name) = variant_kind.binding_pattern()?;
115
116 let target = variant.syntax().text_range();
117 acc.add(
118 AssistId("generate_enum_into_method", AssistKind::Generate),
119 "Generate an `into_` method for an enum variant",
120 target,
121 |builder| {
122 let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
123 let method = format!(
124 " {}fn {}(self) -> Option<{}> {{
125 if let Self::{}{} = self {{
126 Some({})
127 }} else {{
128 None
129 }}
130 }}",
131 vis,
132 fn_name,
133 field_type.syntax(),
134 variant_name,
135 pattern_suffix,
136 bound_name,
137 );
138
139 add_method_to_adt(builder, &parent_enum, impl_def, &method);
140 },
141 )
142}
143
144// Assist: generate_enum_as_method
145//
146// Generate an `as_` method for an enum variant.
147//
148// ```
149// enum Value {
150// Number(i32),
151// Text(String)$0,
152// }
153// ```
154// ->
155// ```
156// enum Value {
157// Number(i32),
158// Text(String),
159// }
160//
161// impl Value {
162// fn as_text(&self) -> Option<&String> {
163// if let Self::Text(v) = self {
164// Some(v)
165// } else {
166// None
167// }
168// }
169// }
170// ```
171pub(crate) fn generate_enum_as_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
172 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
173 let variant_name = variant.name()?;
174 let parent_enum = ast::Adt::Enum(variant.parent_enum());
175 let variant_kind = variant_kind(&variant);
176
177 let fn_name = format!("as_{}", &to_lower_snake_case(variant_name.text()));
178
179 // Return early if we've found an existing new fn
180 let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?;
181
182 let field_type = variant_kind.single_field_type()?;
183 let (pattern_suffix, bound_name) = variant_kind.binding_pattern()?;
184
185 let target = variant.syntax().text_range();
186 acc.add(
187 AssistId("generate_enum_as_method", AssistKind::Generate),
188 "Generate an `as_` method for an enum variant",
189 target,
190 |builder| {
191 let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
192 let method = format!(
193 " {}fn {}(&self) -> Option<&{}> {{
194 if let Self::{}{} = self {{
195 Some({})
196 }} else {{
197 None
198 }}
199 }}",
200 vis,
201 fn_name,
202 field_type.syntax(),
203 variant_name,
204 pattern_suffix,
205 bound_name,
206 );
207
208 add_method_to_adt(builder, &parent_enum, impl_def, &method);
209 },
210 )
211}
212
213fn add_method_to_adt(
214 builder: &mut AssistBuilder,
215 adt: &ast::Adt,
216 impl_def: Option<ast::Impl>,
217 method: &str,
218) {
219 let mut buf = String::with_capacity(method.len() + 2);
220 if impl_def.is_some() {
221 buf.push('\n');
222 }
223 buf.push_str(method);
224
225 let start_offset = impl_def
226 .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
227 .unwrap_or_else(|| {
228 buf = generate_impl_text(&adt, &buf);
229 adt.syntax().text_range().end()
230 });
231
232 builder.insert(start_offset, buf);
233}
234
235enum VariantKind {
236 Unit,
237 /// Tuple with a single field
238 NewtypeTuple {
239 ty: Option<ast::Type>,
240 },
241 /// Tuple with 0 or more than 2 fields
242 Tuple,
243 /// Record with a single field
244 NewtypeRecord {
245 field_name: Option<ast::Name>,
246 field_type: Option<ast::Type>,
247 },
248 /// Record with 0 or more than 2 fields
249 Record,
250}
251
252impl VariantKind {
253 fn pattern_suffix(&self) -> &'static str {
254 match self {
255 VariantKind::Unit => "",
256 VariantKind::NewtypeTuple { .. } | VariantKind::Tuple => "(..)",
257 VariantKind::NewtypeRecord { .. } | VariantKind::Record => " { .. }",
258 }
259 }
260
261 fn binding_pattern(&self) -> Option<(String, String)> {
262 match self {
263 VariantKind::Unit
264 | VariantKind::Tuple
265 | VariantKind::Record
266 | VariantKind::NewtypeRecord { field_name: None, .. } => None,
267 VariantKind::NewtypeTuple { .. } => Some(("(v)".to_owned(), "v".to_owned())),
268 VariantKind::NewtypeRecord { field_name: Some(name), .. } => {
269 Some((format!(" {{ {} }}", name.syntax()), name.syntax().to_string()))
270 }
271 }
272 }
273
274 fn single_field_type(&self) -> Option<&ast::Type> {
275 match self {
276 VariantKind::Unit | VariantKind::Tuple | VariantKind::Record => None,
277 VariantKind::NewtypeTuple { ty } => ty.as_ref(),
278 VariantKind::NewtypeRecord { field_type, .. } => field_type.as_ref(),
279 }
280 }
281}
282
283fn variant_kind(variant: &ast::Variant) -> VariantKind {
284 match variant.kind() {
285 ast::StructKind::Record(record) => {
286 if let Some((single_field,)) = record.fields().collect_tuple() {
287 let field_name = single_field.name();
288 let field_type = single_field.ty();
289 VariantKind::NewtypeRecord { field_name, field_type }
290 } else {
291 VariantKind::Record
292 }
293 }
294 ast::StructKind::Tuple(tuple) => {
295 if let Some((single_field,)) = tuple.fields().collect_tuple() {
296 let ty = single_field.ty();
297 VariantKind::NewtypeTuple { ty }
298 } else {
299 VariantKind::Tuple
300 }
301 }
302 ast::StructKind::Unit => VariantKind::Unit,
303 }
304}
305
306#[cfg(test)] 72#[cfg(test)]
307mod tests { 73mod tests {
308 use crate::tests::{check_assist, check_assist_not_applicable}; 74 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -481,162 +247,4 @@ impl Variant {
481}"#, 247}"#,
482 ); 248 );
483 } 249 }
484
485 #[test]
486 fn test_generate_enum_into_tuple_variant() {
487 check_assist(
488 generate_enum_into_method,
489 r#"
490enum Value {
491 Number(i32),
492 Text(String)$0,
493}"#,
494 r#"enum Value {
495 Number(i32),
496 Text(String),
497}
498
499impl Value {
500 fn into_text(self) -> Option<String> {
501 if let Self::Text(v) = self {
502 Some(v)
503 } else {
504 None
505 }
506 }
507}"#,
508 );
509 }
510
511 #[test]
512 fn test_generate_enum_into_already_implemented() {
513 check_assist_not_applicable(
514 generate_enum_into_method,
515 r#"enum Value {
516 Number(i32),
517 Text(String)$0,
518}
519
520impl Value {
521 fn into_text(self) -> Option<String> {
522 if let Self::Text(v) = self {
523 Some(v)
524 } else {
525 None
526 }
527 }
528}"#,
529 );
530 }
531
532 #[test]
533 fn test_generate_enum_into_unit_variant() {
534 check_assist_not_applicable(
535 generate_enum_into_method,
536 r#"enum Value {
537 Number(i32),
538 Text(String),
539 Unit$0,
540}"#,
541 );
542 }
543
544 #[test]
545 fn test_generate_enum_into_record_with_multiple_fields() {
546 check_assist_not_applicable(
547 generate_enum_into_method,
548 r#"enum Value {
549 Number(i32),
550 Text(String),
551 Both { first: i32, second: String }$0,
552}"#,
553 );
554 }
555
556 #[test]
557 fn test_generate_enum_into_tuple_with_multiple_fields() {
558 check_assist_not_applicable(
559 generate_enum_into_method,
560 r#"enum Value {
561 Number(i32),
562 Text(String, String)$0,
563}"#,
564 );
565 }
566
567 #[test]
568 fn test_generate_enum_into_record_variant() {
569 check_assist(
570 generate_enum_into_method,
571 r#"enum Value {
572 Number(i32),
573 Text { text: String }$0,
574}"#,
575 r#"enum Value {
576 Number(i32),
577 Text { text: String },
578}
579
580impl Value {
581 fn into_text(self) -> Option<String> {
582 if let Self::Text { text } = self {
583 Some(text)
584 } else {
585 None
586 }
587 }
588}"#,
589 );
590 }
591
592 #[test]
593 fn test_generate_enum_as_tuple_variant() {
594 check_assist(
595 generate_enum_as_method,
596 r#"
597enum Value {
598 Number(i32),
599 Text(String)$0,
600}"#,
601 r#"enum Value {
602 Number(i32),
603 Text(String),
604}
605
606impl Value {
607 fn as_text(&self) -> Option<&String> {
608 if let Self::Text(v) = self {
609 Some(v)
610 } else {
611 None
612 }
613 }
614}"#,
615 );
616 }
617
618 #[test]
619 fn test_generate_enum_as_record_variant() {
620 check_assist(
621 generate_enum_as_method,
622 r#"enum Value {
623 Number(i32),
624 Text { text: String }$0,
625}"#,
626 r#"enum Value {
627 Number(i32),
628 Text { text: String },
629}
630
631impl Value {
632 fn as_text(&self) -> Option<&String> {
633 if let Self::Text { text } = self {
634 Some(text)
635 } else {
636 None
637 }
638 }
639}"#,
640 );
641 }
642} 250}