diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-11-24 17:51:46 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-11-24 17:51:46 +0000 |
commit | d2f398cd76659425c9d48185a1be92ee9a9332c2 (patch) | |
tree | d0f3f77261cf35f0ee7f1d3b0dce8ebd13f93191 | |
parent | b769f5da6e51d1da2c981f00c48fb2585f933138 (diff) | |
parent | 377fa7db3f1664dcc46213402b4fb7bd98923475 (diff) |
Merge #6624
6624: Check structs for match exhaustiveness r=Veykril a=Veykril
Co-authored-by: Lukas Wirth <[email protected]>
-rw-r--r-- | crates/hir_ty/src/diagnostics/match_check.rs | 151 |
1 files changed, 120 insertions, 31 deletions
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs index a52f41764..62c329731 100644 --- a/crates/hir_ty/src/diagnostics/match_check.rs +++ b/crates/hir_ty/src/diagnostics/match_check.rs | |||
@@ -223,7 +223,7 @@ use hir_def::{ | |||
223 | adt::VariantData, | 223 | adt::VariantData, |
224 | body::Body, | 224 | body::Body, |
225 | expr::{Expr, Literal, Pat, PatId}, | 225 | expr::{Expr, Literal, Pat, PatId}, |
226 | AdtId, EnumVariantId, VariantId, | 226 | AdtId, EnumVariantId, StructId, VariantId, |
227 | }; | 227 | }; |
228 | use smallvec::{smallvec, SmallVec}; | 228 | use smallvec::{smallvec, SmallVec}; |
229 | 229 | ||
@@ -391,21 +391,28 @@ impl PatStack { | |||
391 | } | 391 | } |
392 | } | 392 | } |
393 | (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?), | 393 | (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?), |
394 | (Pat::Path(_), Constructor::Enum(constructor)) => { | 394 | (Pat::Path(_), constructor) => { |
395 | // unit enum variants become `Pat::Path` | 395 | // unit enum variants become `Pat::Path` |
396 | let pat_id = head.as_id().expect("we know this isn't a wild"); | 396 | let pat_id = head.as_id().expect("we know this isn't a wild"); |
397 | if !enum_variant_matches(cx, pat_id, *constructor) { | 397 | let variant_id: VariantId = match constructor { |
398 | &Constructor::Enum(e) => e.into(), | ||
399 | &Constructor::Struct(s) => s.into(), | ||
400 | _ => return Err(MatchCheckErr::NotImplemented), | ||
401 | }; | ||
402 | if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) { | ||
398 | None | 403 | None |
399 | } else { | 404 | } else { |
400 | Some(self.to_tail()) | 405 | Some(self.to_tail()) |
401 | } | 406 | } |
402 | } | 407 | } |
403 | ( | 408 | (Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, constructor) => { |
404 | Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, | ||
405 | Constructor::Enum(enum_constructor), | ||
406 | ) => { | ||
407 | let pat_id = head.as_id().expect("we know this isn't a wild"); | 409 | let pat_id = head.as_id().expect("we know this isn't a wild"); |
408 | if !enum_variant_matches(cx, pat_id, *enum_constructor) { | 410 | let variant_id: VariantId = match constructor { |
411 | &Constructor::Enum(e) => e.into(), | ||
412 | &Constructor::Struct(s) => s.into(), | ||
413 | _ => return Err(MatchCheckErr::MalformedMatchArm), | ||
414 | }; | ||
415 | if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) { | ||
409 | None | 416 | None |
410 | } else { | 417 | } else { |
411 | let constructor_arity = constructor.arity(cx)?; | 418 | let constructor_arity = constructor.arity(cx)?; |
@@ -443,12 +450,22 @@ impl PatStack { | |||
443 | } | 450 | } |
444 | } | 451 | } |
445 | } | 452 | } |
446 | (Pat::Record { args: ref arg_patterns, .. }, Constructor::Enum(e)) => { | 453 | (Pat::Record { args: ref arg_patterns, .. }, constructor) => { |
447 | let pat_id = head.as_id().expect("we know this isn't a wild"); | 454 | let pat_id = head.as_id().expect("we know this isn't a wild"); |
448 | if !enum_variant_matches(cx, pat_id, *e) { | 455 | let (variant_id, variant_data) = match constructor { |
456 | &Constructor::Enum(e) => ( | ||
457 | e.into(), | ||
458 | cx.db.enum_data(e.parent).variants[e.local_id].variant_data.clone(), | ||
459 | ), | ||
460 | &Constructor::Struct(s) => { | ||
461 | (s.into(), cx.db.struct_data(s).variant_data.clone()) | ||
462 | } | ||
463 | _ => return Err(MatchCheckErr::MalformedMatchArm), | ||
464 | }; | ||
465 | if Some(variant_id) != cx.infer.variant_resolution_for_pat(pat_id) { | ||
449 | None | 466 | None |
450 | } else { | 467 | } else { |
451 | match cx.db.enum_data(e.parent).variants[e.local_id].variant_data.as_ref() { | 468 | match variant_data.as_ref() { |
452 | VariantData::Record(struct_field_arena) => { | 469 | VariantData::Record(struct_field_arena) => { |
453 | // Here we treat any missing fields in the record as the wild pattern, as | 470 | // Here we treat any missing fields in the record as the wild pattern, as |
454 | // if the record has ellipsis. We want to do this here even if the | 471 | // if the record has ellipsis. We want to do this here even if the |
@@ -727,6 +744,7 @@ enum Constructor { | |||
727 | Bool(bool), | 744 | Bool(bool), |
728 | Tuple { arity: usize }, | 745 | Tuple { arity: usize }, |
729 | Enum(EnumVariantId), | 746 | Enum(EnumVariantId), |
747 | Struct(StructId), | ||
730 | } | 748 | } |
731 | 749 | ||
732 | impl Constructor { | 750 | impl Constructor { |
@@ -741,6 +759,11 @@ impl Constructor { | |||
741 | VariantData::Unit => 0, | 759 | VariantData::Unit => 0, |
742 | } | 760 | } |
743 | } | 761 | } |
762 | &Constructor::Struct(s) => match cx.db.struct_data(s).variant_data.as_ref() { | ||
763 | VariantData::Tuple(struct_field_data) => struct_field_data.len(), | ||
764 | VariantData::Record(struct_field_data) => struct_field_data.len(), | ||
765 | VariantData::Unit => 0, | ||
766 | }, | ||
744 | }; | 767 | }; |
745 | 768 | ||
746 | Ok(arity) | 769 | Ok(arity) |
@@ -749,7 +772,7 @@ impl Constructor { | |||
749 | fn all_constructors(&self, cx: &MatchCheckCtx) -> Vec<Constructor> { | 772 | fn all_constructors(&self, cx: &MatchCheckCtx) -> Vec<Constructor> { |
750 | match self { | 773 | match self { |
751 | Constructor::Bool(_) => vec![Constructor::Bool(true), Constructor::Bool(false)], | 774 | Constructor::Bool(_) => vec![Constructor::Bool(true), Constructor::Bool(false)], |
752 | Constructor::Tuple { .. } => vec![*self], | 775 | Constructor::Tuple { .. } | Constructor::Struct(_) => vec![*self], |
753 | Constructor::Enum(e) => cx | 776 | Constructor::Enum(e) => cx |
754 | .db | 777 | .db |
755 | .enum_data(e.parent) | 778 | .enum_data(e.parent) |
@@ -786,6 +809,7 @@ fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Opt | |||
786 | VariantId::EnumVariantId(enum_variant_id) => { | 809 | VariantId::EnumVariantId(enum_variant_id) => { |
787 | Some(Constructor::Enum(enum_variant_id)) | 810 | Some(Constructor::Enum(enum_variant_id)) |
788 | } | 811 | } |
812 | VariantId::StructId(struct_id) => Some(Constructor::Struct(struct_id)), | ||
789 | _ => return Err(MatchCheckErr::NotImplemented), | 813 | _ => return Err(MatchCheckErr::NotImplemented), |
790 | } | 814 | } |
791 | } | 815 | } |
@@ -830,13 +854,13 @@ fn all_constructors_covered( | |||
830 | 854 | ||
831 | false | 855 | false |
832 | }), | 856 | }), |
857 | &Constructor::Struct(s) => used_constructors.iter().any(|constructor| match constructor { | ||
858 | &Constructor::Struct(sid) => sid == s, | ||
859 | _ => false, | ||
860 | }), | ||
833 | } | 861 | } |
834 | } | 862 | } |
835 | 863 | ||
836 | fn enum_variant_matches(cx: &MatchCheckCtx, pat_id: PatId, enum_variant_id: EnumVariantId) -> bool { | ||
837 | Some(enum_variant_id.into()) == cx.infer.variant_resolution_for_pat(pat_id) | ||
838 | } | ||
839 | |||
840 | #[cfg(test)] | 864 | #[cfg(test)] |
841 | mod tests { | 865 | mod tests { |
842 | use crate::diagnostics::tests::check_diagnostics; | 866 | use crate::diagnostics::tests::check_diagnostics; |
@@ -848,8 +872,8 @@ mod tests { | |||
848 | fn main() { | 872 | fn main() { |
849 | match () { } | 873 | match () { } |
850 | //^^ Missing match arm | 874 | //^^ Missing match arm |
851 | match (()) { } | 875 | match (()) { } |
852 | //^^^^ Missing match arm | 876 | //^^^^ Missing match arm |
853 | 877 | ||
854 | match () { _ => (), } | 878 | match () { _ => (), } |
855 | match () { () => (), } | 879 | match () { () => (), } |
@@ -1393,6 +1417,84 @@ fn main() { | |||
1393 | ); | 1417 | ); |
1394 | } | 1418 | } |
1395 | 1419 | ||
1420 | #[test] | ||
1421 | fn record_struct() { | ||
1422 | check_diagnostics( | ||
1423 | r#"struct Foo { a: bool } | ||
1424 | fn main(f: Foo) { | ||
1425 | match f {} | ||
1426 | //^ Missing match arm | ||
1427 | match f { Foo { a: true } => () } | ||
1428 | //^ Missing match arm | ||
1429 | match &f { Foo { a: true } => () } | ||
1430 | //^^ Missing match arm | ||
1431 | match f { Foo { a: _ } => () } | ||
1432 | match f { | ||
1433 | Foo { a: true } => (), | ||
1434 | Foo { a: false } => (), | ||
1435 | } | ||
1436 | match &f { | ||
1437 | Foo { a: true } => (), | ||
1438 | Foo { a: false } => (), | ||
1439 | } | ||
1440 | } | ||
1441 | "#, | ||
1442 | ); | ||
1443 | } | ||
1444 | |||
1445 | #[test] | ||
1446 | fn tuple_struct() { | ||
1447 | check_diagnostics( | ||
1448 | r#"struct Foo(bool); | ||
1449 | fn main(f: Foo) { | ||
1450 | match f {} | ||
1451 | //^ Missing match arm | ||
1452 | match f { Foo(true) => () } | ||
1453 | //^ Missing match arm | ||
1454 | match f { | ||
1455 | Foo(true) => (), | ||
1456 | Foo(false) => (), | ||
1457 | } | ||
1458 | } | ||
1459 | "#, | ||
1460 | ); | ||
1461 | } | ||
1462 | |||
1463 | #[test] | ||
1464 | fn unit_struct() { | ||
1465 | check_diagnostics( | ||
1466 | r#"struct Foo; | ||
1467 | fn main(f: Foo) { | ||
1468 | match f {} | ||
1469 | //^ Missing match arm | ||
1470 | match f { Foo => () } | ||
1471 | } | ||
1472 | "#, | ||
1473 | ); | ||
1474 | } | ||
1475 | |||
1476 | #[test] | ||
1477 | fn record_struct_ellipsis() { | ||
1478 | check_diagnostics( | ||
1479 | r#"struct Foo { foo: bool, bar: bool } | ||
1480 | fn main(f: Foo) { | ||
1481 | match f { Foo { foo: true, .. } => () } | ||
1482 | //^ Missing match arm | ||
1483 | match f { | ||
1484 | //^ Missing match arm | ||
1485 | Foo { foo: true, .. } => (), | ||
1486 | Foo { bar: false, .. } => () | ||
1487 | } | ||
1488 | match f { Foo { .. } => () } | ||
1489 | match f { | ||
1490 | Foo { foo: true, .. } => (), | ||
1491 | Foo { foo: false, .. } => () | ||
1492 | } | ||
1493 | } | ||
1494 | "#, | ||
1495 | ); | ||
1496 | } | ||
1497 | |||
1396 | mod false_negatives { | 1498 | mod false_negatives { |
1397 | //! The implementation of match checking here is a work in progress. As we roll this out, we | 1499 | //! The implementation of match checking here is a work in progress. As we roll this out, we |
1398 | //! prefer false negatives to false positives (ideally there would be no false positives). This | 1500 | //! prefer false negatives to false positives (ideally there would be no false positives). This |
@@ -1434,18 +1536,5 @@ fn main() { | |||
1434 | "#, | 1536 | "#, |
1435 | ); | 1537 | ); |
1436 | } | 1538 | } |
1437 | |||
1438 | #[test] | ||
1439 | fn struct_missing_arm() { | ||
1440 | // We don't currently handle structs. | ||
1441 | check_diagnostics( | ||
1442 | r#" | ||
1443 | struct Foo { a: bool } | ||
1444 | fn main(f: Foo) { | ||
1445 | match f { Foo { a: true } => () } | ||
1446 | } | ||
1447 | "#, | ||
1448 | ); | ||
1449 | } | ||
1450 | } | 1539 | } |
1451 | } | 1540 | } |