Skip to content

Commit 779555c

Browse files
Merge #3884
3884: Match check fix missing pattern panic r=flodiebold a=JoshMcguigan As reported by @cynecx, match arm exhaustiveness checking could panic when tuple enums were missing their pattern. This was reported in the comments of #3706. This fixes the panic, and adds a similar test to ensure tuples don't have this problem. It turns out malformed tuple patterns are caught in the "type check" outside the `is_useful` function, while malformed enum tuple patterns are not. This makes sense to me in hindsight, since the type checker can tell that an enum is the right type even if it is missing its internal pattern, but a tuple (non-enum) just becomes a different type if it is "missing" its pattern. This discrepency is why we report a diagnostic in the tuple case (because all arms are filtered out, so there are missing arms), but not in the enum tuple case (because we return an `Err(MalformedMatchArm)` from `is_useful`). I don't think this is that big of a deal, because in both cases this is malformed code and there should eventually be a `MalformedMatchArm` diagnostic or similar. But perhaps we should change things so that if any arm fails the type check we skip the entire diagnostic? That would at least make these two cases behave in the same way. @flodiebold Co-authored-by: Josh Mcguigan <[email protected]>
2 parents 4762c6d + 36c110e commit 779555c

File tree

1 file changed

+59
-15
lines changed

1 file changed

+59
-15
lines changed

crates/ra_hir_ty/src/_match.rs

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,10 @@ impl From<PatId> for PatIdOrWild {
235235
}
236236

237237
#[derive(Debug, Clone, Copy, PartialEq)]
238-
pub struct MatchCheckNotImplemented;
238+
pub enum MatchCheckErr {
239+
NotImplemented,
240+
MalformedMatchArm,
241+
}
239242

240243
/// The return type of `is_useful` is either an indication of usefulness
241244
/// of the match arm, or an error in the case the match statement
@@ -244,7 +247,7 @@ pub struct MatchCheckNotImplemented;
244247
///
245248
/// The `std::result::Result` type is used here rather than a custom enum
246249
/// to allow the use of `?`.
247-
pub type MatchCheckResult<T> = Result<T, MatchCheckNotImplemented>;
250+
pub type MatchCheckResult<T> = Result<T, MatchCheckErr>;
248251

249252
#[derive(Debug)]
250253
/// A row in a Matrix.
@@ -335,29 +338,36 @@ impl PatStack {
335338
Expr::Literal(Literal::Bool(_)) => None,
336339
// perhaps this is actually unreachable given we have
337340
// already checked that these match arms have the appropriate type?
338-
_ => return Err(MatchCheckNotImplemented),
341+
_ => return Err(MatchCheckErr::NotImplemented),
339342
}
340343
}
341344
(Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?),
342345
(Pat::Path(_), Constructor::Enum(constructor)) => {
343-
// enums with no associated data become `Pat::Path`
346+
// unit enum variants become `Pat::Path`
344347
let pat_id = self.head().as_id().expect("we know this isn't a wild");
345348
if !enum_variant_matches(cx, pat_id, *constructor) {
346349
None
347350
} else {
348351
Some(self.to_tail())
349352
}
350353
}
351-
(Pat::TupleStruct { args: ref pat_ids, .. }, Constructor::Enum(constructor)) => {
354+
(Pat::TupleStruct { args: ref pat_ids, .. }, Constructor::Enum(enum_constructor)) => {
352355
let pat_id = self.head().as_id().expect("we know this isn't a wild");
353-
if !enum_variant_matches(cx, pat_id, *constructor) {
356+
if !enum_variant_matches(cx, pat_id, *enum_constructor) {
354357
None
355358
} else {
359+
// If the enum variant matches, then we need to confirm
360+
// that the number of patterns aligns with the expected
361+
// number of patterns for that enum variant.
362+
if pat_ids.len() != constructor.arity(cx)? {
363+
return Err(MatchCheckErr::MalformedMatchArm);
364+
}
365+
356366
Some(self.replace_head_with(pat_ids))
357367
}
358368
}
359-
(Pat::Or(_), _) => return Err(MatchCheckNotImplemented),
360-
(_, _) => return Err(MatchCheckNotImplemented),
369+
(Pat::Or(_), _) => return Err(MatchCheckErr::NotImplemented),
370+
(_, _) => return Err(MatchCheckErr::NotImplemented),
361371
};
362372

363373
Ok(result)
@@ -514,7 +524,7 @@ pub(crate) fn is_useful(
514524
return if any_useful {
515525
Ok(Usefulness::Useful)
516526
} else if found_unimplemented {
517-
Err(MatchCheckNotImplemented)
527+
Err(MatchCheckErr::NotImplemented)
518528
} else {
519529
Ok(Usefulness::NotUseful)
520530
};
@@ -567,7 +577,7 @@ pub(crate) fn is_useful(
567577
}
568578

569579
if found_unimplemented {
570-
Err(MatchCheckNotImplemented)
580+
Err(MatchCheckErr::NotImplemented)
571581
} else {
572582
Ok(Usefulness::NotUseful)
573583
}
@@ -604,7 +614,7 @@ impl Constructor {
604614
match cx.db.enum_data(e.parent).variants[e.local_id].variant_data.as_ref() {
605615
VariantData::Tuple(struct_field_data) => struct_field_data.len(),
606616
VariantData::Unit => 0,
607-
_ => return Err(MatchCheckNotImplemented),
617+
_ => return Err(MatchCheckErr::NotImplemented),
608618
}
609619
}
610620
};
@@ -637,20 +647,20 @@ fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> MatchCheckResult<Opt
637647
Pat::Tuple(pats) => Some(Constructor::Tuple { arity: pats.len() }),
638648
Pat::Lit(lit_expr) => match cx.body.exprs[lit_expr] {
639649
Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)),
640-
_ => return Err(MatchCheckNotImplemented),
650+
_ => return Err(MatchCheckErr::NotImplemented),
641651
},
642652
Pat::TupleStruct { .. } | Pat::Path(_) => {
643653
let pat_id = pat.as_id().expect("we already know this pattern is not a wild");
644654
let variant_id =
645-
cx.infer.variant_resolution_for_pat(pat_id).ok_or(MatchCheckNotImplemented)?;
655+
cx.infer.variant_resolution_for_pat(pat_id).ok_or(MatchCheckErr::NotImplemented)?;
646656
match variant_id {
647657
VariantId::EnumVariantId(enum_variant_id) => {
648658
Some(Constructor::Enum(enum_variant_id))
649659
}
650-
_ => return Err(MatchCheckNotImplemented),
660+
_ => return Err(MatchCheckErr::NotImplemented),
651661
}
652662
}
653-
_ => return Err(MatchCheckNotImplemented),
663+
_ => return Err(MatchCheckErr::NotImplemented),
654664
};
655665

656666
Ok(res)
@@ -1324,6 +1334,40 @@ mod tests {
13241334
check_diagnostic(content);
13251335
}
13261336

1337+
#[test]
1338+
fn malformed_match_arm_tuple_missing_pattern() {
1339+
let content = r"
1340+
fn test_fn() {
1341+
match (0) {
1342+
() => (),
1343+
}
1344+
}
1345+
";
1346+
1347+
// Match arms with the incorrect type are filtered out.
1348+
check_diagnostic(content);
1349+
}
1350+
1351+
#[test]
1352+
fn malformed_match_arm_tuple_enum_missing_pattern() {
1353+
let content = r"
1354+
enum Either {
1355+
A,
1356+
B(u32),
1357+
}
1358+
fn test_fn() {
1359+
match Either::A {
1360+
Either::A => (),
1361+
Either::B() => (),
1362+
}
1363+
}
1364+
";
1365+
1366+
// We are testing to be sure we don't panic here when the match
1367+
// arm `Either::B` is missing its pattern.
1368+
check_no_diagnostic(content);
1369+
}
1370+
13271371
#[test]
13281372
fn enum_not_in_scope() {
13291373
let content = r"

0 commit comments

Comments
 (0)