diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-03-23 16:41:46 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-03-23 16:41:46 +0000 |
commit | eff1b3fe4d17dcecf0ec9a30c35d6c88715cb8ea (patch) | |
tree | 7f5abbc9336a3476de08d8ee5c3284b8d41db72d | |
parent | b605271d7f3fa3fd3ac4dd0e1520b80b5fb13b40 (diff) | |
parent | bc48c9d5116f08efea26da94c82a3eaa1622fc5d (diff) |
Merge #3689
3689: implement fill match arm assist for tuple of enums r=matklad a=JoshMcguigan
This updates the fill match arm assist to work in cases where the user is matching on a tuple of enums.
Note, for now this does not apply when some match arms exist (other than the trivial `_`), but I think this could be added in the future.
I think this also lays the groundwork for filling match arms when matching on tuples of non-enum values, for example a tuple of an enum and a boolean.
Co-authored-by: Josh Mcguigan <[email protected]>
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/ra_assists/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/fill_match_arms.rs | 255 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 14 |
4 files changed, 257 insertions, 14 deletions
diff --git a/Cargo.lock b/Cargo.lock index f8d26192d..eb5247b69 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -876,6 +876,7 @@ name = "ra_assists" | |||
876 | version = "0.1.0" | 876 | version = "0.1.0" |
877 | dependencies = [ | 877 | dependencies = [ |
878 | "format-buf", | 878 | "format-buf", |
879 | "itertools", | ||
879 | "join_to_string", | 880 | "join_to_string", |
880 | "ra_db", | 881 | "ra_db", |
881 | "ra_fmt", | 882 | "ra_fmt", |
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index d314dc8e6..85adddb5b 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -11,6 +11,7 @@ doctest = false | |||
11 | format-buf = "1.0.0" | 11 | format-buf = "1.0.0" |
12 | join_to_string = "0.1.3" | 12 | join_to_string = "0.1.3" |
13 | rustc-hash = "1.1.0" | 13 | rustc-hash = "1.1.0" |
14 | itertools = "0.8.2" | ||
14 | 15 | ||
15 | ra_syntax = { path = "../ra_syntax" } | 16 | ra_syntax = { path = "../ra_syntax" } |
16 | ra_text_edit = { path = "../ra_text_edit" } | 17 | ra_text_edit = { path = "../ra_text_edit" } |
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index fbd6a3ec3..7463b2af7 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -3,6 +3,7 @@ | |||
3 | use std::iter; | 3 | use std::iter; |
4 | 4 | ||
5 | use hir::{Adt, HasSource, Semantics}; | 5 | use hir::{Adt, HasSource, Semantics}; |
6 | use itertools::Itertools; | ||
6 | use ra_ide_db::RootDatabase; | 7 | use ra_ide_db::RootDatabase; |
7 | 8 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use crate::{Assist, AssistCtx, AssistId}; |
@@ -39,13 +40,6 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
39 | let match_arm_list = match_expr.match_arm_list()?; | 40 | let match_arm_list = match_expr.match_arm_list()?; |
40 | 41 | ||
41 | let expr = match_expr.expr()?; | 42 | let expr = match_expr.expr()?; |
42 | let enum_def = resolve_enum_def(&ctx.sema, &expr)?; | ||
43 | let module = ctx.sema.scope(expr.syntax()).module()?; | ||
44 | |||
45 | let variants = enum_def.variants(ctx.db); | ||
46 | if variants.is_empty() { | ||
47 | return None; | ||
48 | } | ||
49 | 43 | ||
50 | let mut arms: Vec<MatchArm> = match_arm_list.arms().collect(); | 44 | let mut arms: Vec<MatchArm> = match_arm_list.arms().collect(); |
51 | if arms.len() == 1 { | 45 | if arms.len() == 1 { |
@@ -54,13 +48,49 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
54 | } | 48 | } |
55 | } | 49 | } |
56 | 50 | ||
57 | let db = ctx.db; | 51 | let module = ctx.sema.scope(expr.syntax()).module()?; |
58 | let missing_arms: Vec<MatchArm> = variants | 52 | |
59 | .into_iter() | 53 | let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { |
60 | .filter_map(|variant| build_pat(db, module, variant)) | 54 | let variants = enum_def.variants(ctx.db); |
61 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 55 | |
62 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())) | 56 | variants |
63 | .collect(); | 57 | .into_iter() |
58 | .filter_map(|variant| build_pat(ctx.db, module, variant)) | ||
59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | ||
60 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())) | ||
61 | .collect() | ||
62 | } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { | ||
63 | // Partial fill not currently supported for tuple of enums. | ||
64 | if !arms.is_empty() { | ||
65 | return None; | ||
66 | } | ||
67 | |||
68 | // We do not currently support filling match arms for a tuple | ||
69 | // containing a single enum. | ||
70 | if enum_defs.len() < 2 { | ||
71 | return None; | ||
72 | } | ||
73 | |||
74 | // When calculating the match arms for a tuple of enums, we want | ||
75 | // to create a match arm for each possible combination of enum | ||
76 | // values. The `multi_cartesian_product` method transforms | ||
77 | // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)> | ||
78 | // where each tuple represents a proposed match arm. | ||
79 | enum_defs | ||
80 | .into_iter() | ||
81 | .map(|enum_def| enum_def.variants(ctx.db)) | ||
82 | .multi_cartesian_product() | ||
83 | .map(|variants| { | ||
84 | let patterns = | ||
85 | variants.into_iter().filter_map(|variant| build_pat(ctx.db, module, variant)); | ||
86 | ast::Pat::from(make::tuple_pat(patterns)) | ||
87 | }) | ||
88 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | ||
89 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())) | ||
90 | .collect() | ||
91 | } else { | ||
92 | return None; | ||
93 | }; | ||
64 | 94 | ||
65 | if missing_arms.is_empty() { | 95 | if missing_arms.is_empty() { |
66 | return None; | 96 | return None; |
@@ -104,6 +134,25 @@ fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option< | |||
104 | }) | 134 | }) |
105 | } | 135 | } |
106 | 136 | ||
137 | fn resolve_tuple_of_enum_def( | ||
138 | sema: &Semantics<RootDatabase>, | ||
139 | expr: &ast::Expr, | ||
140 | ) -> Option<Vec<hir::Enum>> { | ||
141 | sema.type_of_expr(&expr)? | ||
142 | .tuple_fields(sema.db) | ||
143 | .iter() | ||
144 | .map(|ty| { | ||
145 | ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { | ||
146 | Some(Adt::Enum(e)) => Some(e), | ||
147 | // For now we only handle expansion for a tuple of enums. Here | ||
148 | // we map non-enum items to None and rely on `collect` to | ||
149 | // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. | ||
150 | _ => None, | ||
151 | }) | ||
152 | }) | ||
153 | .collect() | ||
154 | } | ||
155 | |||
107 | fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> { | 156 | fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> { |
108 | let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?); | 157 | let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?); |
109 | 158 | ||
@@ -152,6 +201,21 @@ mod tests { | |||
152 | } | 201 | } |
153 | 202 | ||
154 | #[test] | 203 | #[test] |
204 | fn tuple_of_non_enum() { | ||
205 | // for now this case is not handled, although it potentially could be | ||
206 | // in the future | ||
207 | check_assist_not_applicable( | ||
208 | fill_match_arms, | ||
209 | r#" | ||
210 | fn main() { | ||
211 | match (0, false)<|> { | ||
212 | } | ||
213 | } | ||
214 | "#, | ||
215 | ); | ||
216 | } | ||
217 | |||
218 | #[test] | ||
155 | fn partial_fill_record_tuple() { | 219 | fn partial_fill_record_tuple() { |
156 | check_assist( | 220 | check_assist( |
157 | fill_match_arms, | 221 | fill_match_arms, |
@@ -308,6 +372,169 @@ mod tests { | |||
308 | } | 372 | } |
309 | 373 | ||
310 | #[test] | 374 | #[test] |
375 | fn fill_match_arms_tuple_of_enum() { | ||
376 | check_assist( | ||
377 | fill_match_arms, | ||
378 | r#" | ||
379 | enum A { | ||
380 | One, | ||
381 | Two, | ||
382 | } | ||
383 | enum B { | ||
384 | One, | ||
385 | Two, | ||
386 | } | ||
387 | |||
388 | fn main() { | ||
389 | let a = A::One; | ||
390 | let b = B::One; | ||
391 | match (a<|>, b) {} | ||
392 | } | ||
393 | "#, | ||
394 | r#" | ||
395 | enum A { | ||
396 | One, | ||
397 | Two, | ||
398 | } | ||
399 | enum B { | ||
400 | One, | ||
401 | Two, | ||
402 | } | ||
403 | |||
404 | fn main() { | ||
405 | let a = A::One; | ||
406 | let b = B::One; | ||
407 | match <|>(a, b) { | ||
408 | (A::One, B::One) => (), | ||
409 | (A::One, B::Two) => (), | ||
410 | (A::Two, B::One) => (), | ||
411 | (A::Two, B::Two) => (), | ||
412 | } | ||
413 | } | ||
414 | "#, | ||
415 | ); | ||
416 | } | ||
417 | |||
418 | #[test] | ||
419 | fn fill_match_arms_tuple_of_enum_ref() { | ||
420 | check_assist( | ||
421 | fill_match_arms, | ||
422 | r#" | ||
423 | enum A { | ||
424 | One, | ||
425 | Two, | ||
426 | } | ||
427 | enum B { | ||
428 | One, | ||
429 | Two, | ||
430 | } | ||
431 | |||
432 | fn main() { | ||
433 | let a = A::One; | ||
434 | let b = B::One; | ||
435 | match (&a<|>, &b) {} | ||
436 | } | ||
437 | "#, | ||
438 | r#" | ||
439 | enum A { | ||
440 | One, | ||
441 | Two, | ||
442 | } | ||
443 | enum B { | ||
444 | One, | ||
445 | Two, | ||
446 | } | ||
447 | |||
448 | fn main() { | ||
449 | let a = A::One; | ||
450 | let b = B::One; | ||
451 | match <|>(&a, &b) { | ||
452 | (A::One, B::One) => (), | ||
453 | (A::One, B::Two) => (), | ||
454 | (A::Two, B::One) => (), | ||
455 | (A::Two, B::Two) => (), | ||
456 | } | ||
457 | } | ||
458 | "#, | ||
459 | ); | ||
460 | } | ||
461 | |||
462 | #[test] | ||
463 | fn fill_match_arms_tuple_of_enum_partial() { | ||
464 | check_assist_not_applicable( | ||
465 | fill_match_arms, | ||
466 | r#" | ||
467 | enum A { | ||
468 | One, | ||
469 | Two, | ||
470 | } | ||
471 | enum B { | ||
472 | One, | ||
473 | Two, | ||
474 | } | ||
475 | |||
476 | fn main() { | ||
477 | let a = A::One; | ||
478 | let b = B::One; | ||
479 | match (a<|>, b) { | ||
480 | (A::Two, B::One) => (), | ||
481 | } | ||
482 | } | ||
483 | "#, | ||
484 | ); | ||
485 | } | ||
486 | |||
487 | #[test] | ||
488 | fn fill_match_arms_tuple_of_enum_not_applicable() { | ||
489 | check_assist_not_applicable( | ||
490 | fill_match_arms, | ||
491 | r#" | ||
492 | enum A { | ||
493 | One, | ||
494 | Two, | ||
495 | } | ||
496 | enum B { | ||
497 | One, | ||
498 | Two, | ||
499 | } | ||
500 | |||
501 | fn main() { | ||
502 | let a = A::One; | ||
503 | let b = B::One; | ||
504 | match (a<|>, b) { | ||
505 | (A::Two, B::One) => (), | ||
506 | (A::One, B::One) => (), | ||
507 | (A::One, B::Two) => (), | ||
508 | (A::Two, B::Two) => (), | ||
509 | } | ||
510 | } | ||
511 | "#, | ||
512 | ); | ||
513 | } | ||
514 | |||
515 | #[test] | ||
516 | fn fill_match_arms_single_element_tuple_of_enum() { | ||
517 | // For now we don't hande the case of a single element tuple, but | ||
518 | // we could handle this in the future if `make::tuple_pat` allowed | ||
519 | // creating a tuple with a single pattern. | ||
520 | check_assist_not_applicable( | ||
521 | fill_match_arms, | ||
522 | r#" | ||
523 | enum A { | ||
524 | One, | ||
525 | Two, | ||
526 | } | ||
527 | |||
528 | fn main() { | ||
529 | let a = A::One; | ||
530 | match (a<|>, ) { | ||
531 | } | ||
532 | } | ||
533 | "#, | ||
534 | ); | ||
535 | } | ||
536 | |||
537 | #[test] | ||
311 | fn test_fill_match_arm_refs() { | 538 | fn test_fill_match_arm_refs() { |
312 | check_assist( | 539 | check_assist( |
313 | fill_match_arms, | 540 | fill_match_arms, |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 9f6f1cc53..9257ccd1a 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -136,6 +136,20 @@ pub fn placeholder_pat() -> ast::PlaceholderPat { | |||
136 | } | 136 | } |
137 | } | 137 | } |
138 | 138 | ||
139 | /// Creates a tuple of patterns from an interator of patterns. | ||
140 | /// | ||
141 | /// Invariant: `pats` must be length > 1 | ||
142 | /// | ||
143 | /// FIXME handle `pats` length == 1 | ||
144 | pub fn tuple_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::TuplePat { | ||
145 | let pats_str = pats.into_iter().map(|p| p.to_string()).join(", "); | ||
146 | return from_text(&format!("({})", pats_str)); | ||
147 | |||
148 | fn from_text(text: &str) -> ast::TuplePat { | ||
149 | ast_from_text(&format!("fn f({}: ())", text)) | ||
150 | } | ||
151 | } | ||
152 | |||
139 | pub fn tuple_struct_pat( | 153 | pub fn tuple_struct_pat( |
140 | path: ast::Path, | 154 | path: ast::Path, |
141 | pats: impl IntoIterator<Item = ast::Pat>, | 155 | pats: impl IntoIterator<Item = ast::Pat>, |