aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2020-11-24 17:28:55 +0000
committerLukas Wirth <[email protected]>2020-11-24 17:50:56 +0000
commit377fa7db3f1664dcc46213402b4fb7bd98923475 (patch)
treed0f3f77261cf35f0ee7f1d3b0dce8ebd13f93191 /crates/hir_ty
parentb769f5da6e51d1da2c981f00c48fb2585f933138 (diff)
Check structs for match exhaustiveness
Diffstat (limited to 'crates/hir_ty')
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs151
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};
228use smallvec::{smallvec, SmallVec}; 228use 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
732impl Constructor { 750impl 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
836fn 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)]
841mod tests { 865mod tests {
842 use crate::diagnostics::tests::check_diagnostics; 866 use crate::diagnostics::tests::check_diagnostics;
@@ -848,8 +872,8 @@ mod tests {
848fn main() { 872fn 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 }
1424fn 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);
1449fn 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;
1467fn 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 }
1480fn 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#"
1443struct Foo { a: bool }
1444fn main(f: Foo) {
1445 match f { Foo { a: true } => () }
1446}
1447"#,
1448 );
1449 }
1450 } 1539 }
1451} 1540}