aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r--crates/ide_assists/src/handlers/generate_enum_match_method.rs265
-rw-r--r--crates/ide_assists/src/lib.rs1
-rw-r--r--crates/ide_assists/src/tests/generated.rs29
3 files changed, 275 insertions, 20 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 38aca0c88..25565d4cc 100644
--- a/crates/ide_assists/src/handlers/generate_enum_match_method.rs
+++ b/crates/ide_assists/src/handlers/generate_enum_match_method.rs
@@ -1,3 +1,4 @@
1use itertools::Itertools;
1use stdx::{format_to, to_lower_snake_case}; 2use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner; 3use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner}; 4use syntax::ast::{self, AstNode, NameOwner};
@@ -88,14 +89,104 @@ pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) ->
88 ) 89 )
89} 90}
90 91
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// ```
119pub(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
91enum VariantKind { 179enum VariantKind {
92 Unit, 180 Unit,
93 /// Tuple with a single field 181 /// Tuple with a single field
94 NewtypeTuple, 182 NewtypeTuple { ty: Option<ast::Type> },
95 /// Tuple with 0 or more than 2 fields 183 /// Tuple with 0 or more than 2 fields
96 Tuple, 184 Tuple,
97 /// Record with a single field 185 /// 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 },
99 /// Record with 0 or more than 2 fields 190 /// Record with 0 or more than 2 fields
100 Record, 191 Record,
101} 192}
@@ -104,27 +195,57 @@ impl VariantKind {
104 fn pattern_suffix(&self) -> &'static str { 195 fn pattern_suffix(&self) -> &'static str {
105 match self { 196 match self {
106 VariantKind::Unit => "", 197 VariantKind::Unit => "",
107 VariantKind::NewtypeTuple | 198 VariantKind::NewtypeTuple { .. } |
108 VariantKind::Tuple => "(..)", 199 VariantKind::Tuple => "(..)",
109 VariantKind::NewtypeRecord { .. } | 200 VariantKind::NewtypeRecord { .. } |
110 VariantKind::Record => " { .. }", 201 VariantKind::Record => " { .. }",
111 } 202 }
112 } 203 }
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 }
113} 232}
114 233
115fn variant_kind(variant: &ast::Variant) -> VariantKind { 234fn variant_kind(variant: &ast::Variant) -> VariantKind {
116 match variant.kind() { 235 match variant.kind() {
117 ast::StructKind::Record(record) => { 236 ast::StructKind::Record(record) => {
118 if record.fields().count() == 1 { 237 if let Some((single_field,)) = record.fields().collect_tuple() {
119 let field_name = record.fields().nth(0).unwrap().name(); 238 let field_name = single_field.name();
120 VariantKind::NewtypeRecord { field_name } 239 let field_type = single_field.ty();
240 VariantKind::NewtypeRecord { field_name, field_type }
121 } else { 241 } else {
122 VariantKind::Record 242 VariantKind::Record
123 } 243 }
124 } 244 }
125 ast::StructKind::Tuple(tuple) => { 245 ast::StructKind::Tuple(tuple) => {
126 if tuple.fields().count() == 1 { 246 if let Some((single_field,)) = tuple.fields().collect_tuple() {
127 VariantKind::NewtypeTuple 247 let ty = single_field.ty();
248 VariantKind::NewtypeTuple { ty }
128 } else { 249 } else {
129 VariantKind::Tuple 250 VariantKind::Tuple
130 } 251 }
@@ -139,12 +260,8 @@ mod tests {
139 260
140 use super::*; 261 use super::*;
141 262
142 fn check_not_applicable(ra_fixture: &str) {
143 check_assist_not_applicable(generate_enum_is_method, ra_fixture)
144 }
145
146 #[test] 263 #[test]
147 fn test_generate_enum_match_from_variant() { 264 fn test_generate_enum_is_from_variant() {
148 check_assist( 265 check_assist(
149 generate_enum_is_method, 266 generate_enum_is_method,
150 r#" 267 r#"
@@ -169,8 +286,9 @@ impl Variant {
169 } 286 }
170 287
171 #[test] 288 #[test]
172 fn test_generate_enum_match_already_implemented() { 289 fn test_generate_enum_is_already_implemented() {
173 check_not_applicable( 290 check_assist_not_applicable(
291 generate_enum_is_method,
174 r#" 292 r#"
175enum Variant { 293enum Variant {
176 Undefined, 294 Undefined,
@@ -187,7 +305,7 @@ impl Variant {
187 } 305 }
188 306
189 #[test] 307 #[test]
190 fn test_generate_enum_match_from_tuple_variant() { 308 fn test_generate_enum_is_from_tuple_variant() {
191 check_assist( 309 check_assist(
192 generate_enum_is_method, 310 generate_enum_is_method,
193 r#" 311 r#"
@@ -212,7 +330,7 @@ impl Variant {
212 } 330 }
213 331
214 #[test] 332 #[test]
215 fn test_generate_enum_match_from_record_variant() { 333 fn test_generate_enum_is_from_record_variant() {
216 check_assist( 334 check_assist(
217 generate_enum_is_method, 335 generate_enum_is_method,
218 r#" 336 r#"
@@ -237,7 +355,7 @@ impl Variant {
237 } 355 }
238 356
239 #[test] 357 #[test]
240 fn test_generate_enum_match_from_variant_with_one_variant() { 358 fn test_generate_enum_is_from_variant_with_one_variant() {
241 check_assist( 359 check_assist(
242 generate_enum_is_method, 360 generate_enum_is_method,
243 r#"enum Variant { Undefi$0ned }"#, 361 r#"enum Variant { Undefi$0ned }"#,
@@ -254,7 +372,7 @@ impl Variant {
254 } 372 }
255 373
256 #[test] 374 #[test]
257 fn test_generate_enum_match_from_variant_with_visibility_marker() { 375 fn test_generate_enum_is_from_variant_with_visibility_marker() {
258 check_assist( 376 check_assist(
259 generate_enum_is_method, 377 generate_enum_is_method,
260 r#" 378 r#"
@@ -279,7 +397,7 @@ impl Variant {
279 } 397 }
280 398
281 #[test] 399 #[test]
282 fn test_multiple_generate_enum_match_from_variant() { 400 fn test_multiple_generate_enum_is_from_variant() {
283 check_assist( 401 check_assist(
284 generate_enum_is_method, 402 generate_enum_is_method,
285 r#" 403 r#"
@@ -314,4 +432,111 @@ impl Variant {
314}"#, 432}"#,
315 ); 433 );
316 } 434 }
435
436 #[test]
437 fn test_generate_enum_into_tuple_variant() {
438 check_assist(
439 generate_enum_into_method,
440 r#"
441enum Value {
442 Number(i32),
443 Text(String)$0,
444}"#,
445 r#"enum Value {
446 Number(i32),
447 Text(String),
448}
449
450impl 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
471impl 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
531impl Value {
532 fn into_text(self) -> Option<String> {
533 if let Self::Text { text } = self {
534 Some(text)
535 } else {
536 None
537 }
538 }
539}"#,
540 );
541 }
317} 542}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 601199cd0..2ce59e39a 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -190,6 +190,7 @@ mod handlers {
190 generate_default_from_enum_variant::generate_default_from_enum_variant, 190 generate_default_from_enum_variant::generate_default_from_enum_variant,
191 generate_derive::generate_derive, 191 generate_derive::generate_derive,
192 generate_enum_match_method::generate_enum_is_method, 192 generate_enum_match_method::generate_enum_is_method,
193 generate_enum_match_method::generate_enum_into_method,
193 generate_from_impl_for_enum::generate_from_impl_for_enum, 194 generate_from_impl_for_enum::generate_from_impl_for_enum,
194 generate_function::generate_function, 195 generate_function::generate_function,
195 generate_getter::generate_getter, 196 generate_getter::generate_getter,
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 44bede0d9..39f48dd76 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -483,6 +483,35 @@ struct Point {
483} 483}
484 484
485#[test] 485#[test]
486fn doctest_generate_enum_into_method() {
487 check_doc_test(
488 "generate_enum_into_method",
489 r#####"
490enum Value {
491 Number(i32),
492 Text(String)$0,
493}
494"#####,
495 r#####"
496enum Value {
497 Number(i32),
498 Text(String),
499}
500
501impl Value {
502 fn into_text(self) -> Option<String> {
503 if let Self::Text(v) = self {
504 Some(v)
505 } else {
506 None
507 }
508 }
509}
510"#####,
511 )
512}
513
514#[test]
486fn doctest_generate_enum_is_method() { 515fn doctest_generate_enum_is_method() {
487 check_doc_test( 516 check_doc_test(
488 "generate_enum_is_method", 517 "generate_enum_is_method",