diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/handlers/generate_enum_match_method.rs | 265 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 29 |
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 @@ | |||
1 | use itertools::Itertools; | ||
1 | use stdx::{format_to, to_lower_snake_case}; | 2 | use stdx::{format_to, to_lower_snake_case}; |
2 | use syntax::ast::VisibilityOwner; | 3 | use syntax::ast::VisibilityOwner; |
3 | use syntax::ast::{self, AstNode, NameOwner}; | 4 | use 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 | // ``` | ||
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 | |||
91 | enum VariantKind { | 179 | enum 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 | ||
115 | fn variant_kind(variant: &ast::Variant) -> VariantKind { | 234 | fn 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#" |
175 | enum Variant { | 293 | enum 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#" | ||
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 | } | ||
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] |
486 | fn doctest_generate_enum_into_method() { | ||
487 | check_doc_test( | ||
488 | "generate_enum_into_method", | ||
489 | r#####" | ||
490 | enum Value { | ||
491 | Number(i32), | ||
492 | Text(String)$0, | ||
493 | } | ||
494 | "#####, | ||
495 | r#####" | ||
496 | enum Value { | ||
497 | Number(i32), | ||
498 | Text(String), | ||
499 | } | ||
500 | |||
501 | impl 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] | ||
486 | fn doctest_generate_enum_is_method() { | 515 | fn doctest_generate_enum_is_method() { |
487 | check_doc_test( | 516 | check_doc_test( |
488 | "generate_enum_is_method", | 517 | "generate_enum_is_method", |