Skip to content

Commit d2f398c

Browse files
bors[bot]Veykril
andauthored
Merge #6624
6624: Check structs for match exhaustiveness r=Veykril a=Veykril Co-authored-by: Lukas Wirth <[email protected]>
2 parents b769f5d + 377fa7d commit d2f398c

File tree

1 file changed

+120
-31
lines changed

1 file changed

+120
-31
lines changed

crates/hir_ty/src/diagnostics/match_check.rs

Lines changed: 120 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ use hir_def::{
223223
adt::VariantData,
224224
body::Body,
225225
expr::{Expr, Literal, Pat, PatId},
226-
AdtId, EnumVariantId, VariantId,
226+
AdtId, EnumVariantId, StructId, VariantId,
227227
};
228228
use smallvec::{smallvec, SmallVec};
229229

@@ -391,21 +391,28 @@ impl PatStack {
391391
}
392392
}
393393
(Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?),
394-
(Pat::Path(_), Constructor::Enum(constructor)) => {
394+
(Pat::Path(_), constructor) => {
395395
// unit enum variants become `Pat::Path`
396396
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) {
398403
None
399404
} else {
400405
Some(self.to_tail())
401406
}
402407
}
403-
(
404-
Pat::TupleStruct { args: ref pat_ids, ellipsis, .. },
405-
Constructor::Enum(enum_constructor),
406-
) => {
408+
(Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, constructor) => {
407409
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) {
409416
None
410417
} else {
411418
let constructor_arity = constructor.arity(cx)?;
@@ -443,12 +450,22 @@ impl PatStack {
443450
}
444451
}
445452
}
446-
(Pat::Record { args: ref arg_patterns, .. }, Constructor::Enum(e)) => {
453+
(Pat::Record { args: ref arg_patterns, .. }, constructor) => {
447454
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) {
449466
None
450467
} else {
451-
match cx.db.enum_data(e.parent).variants[e.local_id].variant_data.as_ref() {
468+
match variant_data.as_ref() {
452469
VariantData::Record(struct_field_arena) => {
453470
// Here we treat any missing fields in the record as the wild pattern, as
454471
// if the record has ellipsis. We want to do this here even if the
@@ -727,6 +744,7 @@ enum Constructor {
727744
Bool(bool),
728745
Tuple { arity: usize },
729746
Enum(EnumVariantId),
747+
Struct(StructId),
730748
}
731749

732750
impl Constructor {
@@ -741,6 +759,11 @@ impl Constructor {
741759
VariantData::Unit => 0,
742760
}
743761
}
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+
},
744767
};
745768

746769
Ok(arity)
@@ -749,7 +772,7 @@ impl Constructor {
749772
fn all_constructors(&self, cx: &MatchCheckCtx) -> Vec<Constructor> {
750773
match self {
751774
Constructor::Bool(_) => vec![Constructor::Bool(true), Constructor::Bool(false)],
752-
Constructor::Tuple { .. } => vec![*self],
775+
Constructor::Tuple { .. } | Constructor::Struct(_) => vec![*self],
753776
Constructor::Enum(e) => cx
754777
.db
755778
.enum_data(e.parent)
@@ -786,6 +809,7 @@ fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Opt
786809
VariantId::EnumVariantId(enum_variant_id) => {
787810
Some(Constructor::Enum(enum_variant_id))
788811
}
812+
VariantId::StructId(struct_id) => Some(Constructor::Struct(struct_id)),
789813
_ => return Err(MatchCheckErr::NotImplemented),
790814
}
791815
}
@@ -830,13 +854,13 @@ fn all_constructors_covered(
830854

831855
false
832856
}),
857+
&Constructor::Struct(s) => used_constructors.iter().any(|constructor| match constructor {
858+
&Constructor::Struct(sid) => sid == s,
859+
_ => false,
860+
}),
833861
}
834862
}
835863

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-
840864
#[cfg(test)]
841865
mod tests {
842866
use crate::diagnostics::tests::check_diagnostics;
@@ -848,8 +872,8 @@ mod tests {
848872
fn main() {
849873
match () { }
850874
//^^ Missing match arm
851-
match (()) { }
852-
//^^^^ Missing match arm
875+
match (()) { }
876+
//^^^^ Missing match arm
853877
854878
match () { _ => (), }
855879
match () { () => (), }
@@ -1393,6 +1417,84 @@ fn main() {
13931417
);
13941418
}
13951419

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+
13961498
mod false_negatives {
13971499
//! The implementation of match checking here is a work in progress. As we roll this out, we
13981500
//! prefer false negatives to false positives (ideally there would be no false positives). This
@@ -1431,19 +1533,6 @@ fn main() {
14311533
Either::A(true | false) => (),
14321534
}
14331535
}
1434-
"#,
1435-
);
1436-
}
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-
}
14471536
"#,
14481537
);
14491538
}

0 commit comments

Comments
 (0)