Rollup merge of #154938 - jakubadamw:issue-104653, r=Nadrieril

match exhaustiveness: Show the guard exhaustivity note only when it's the guards alone that cause non-exhaustiveness

Only show the "match arms with guards don't count towards exhaustivity" note when removing all guards would make the match exhaustive, but also in the cases when the match contains arms without guards. Previously, this note was shown only if all arms had guards, but even if the patterns themselves were insufficient to cover all valid values of a type.

Do this by rerunning the exhaustiveness analysis with guards stripped to determine whether the guards are actually the cause of non-exhaustiveness. This only happens on an actual exhaustiveness error, so should not be a performance concern.

This will make a program like:

```rust
fn main() {
    let some_condition = true;
    let some_option: Option<u8> = None;

    let _res = match some_option {
        Some(val) if some_condition => val,
        None => 0,
    };
}
```

produce the note ”match arms with guards don't count towards exhaustivity” that previously would not have been appearing.

Closes rust-lang/rust#104653 as I think this addresses the spirit of that issue. I don’t believe it’s necessary to be any more elaborate in the diagnostics here?
This commit is contained in:
Jonathan Brouwer
2026-04-08 14:22:06 +02:00
committed by GitHub
11 changed files with 24 additions and 4 deletions
@@ -532,6 +532,18 @@ fn check_match(
| hir::MatchSource::AwaitDesugar
| hir::MatchSource::FormatArgs => None,
};
// Check if the match would be exhaustive if all guards were removed.
// If so, we leave a note that guards don't count towards exhaustivity.
let would_be_exhaustive_without_guards = {
let any_arm_has_guard = tarms.iter().any(|arm| arm.has_guard);
any_arm_has_guard && {
let guardless_arms: Vec<_> =
tarms.iter().map(|arm| MatchArm { has_guard: false, ..*arm }).collect();
rustc_pattern_analysis::rustc::analyze_match(&cx, &guardless_arms, scrut.ty)
.is_ok_and(|report| report.non_exhaustiveness_witnesses.is_empty())
}
};
self.error = Err(report_non_exhaustive_match(
&cx,
self.thir,
@@ -540,6 +552,7 @@ fn check_match(
witnesses,
arms,
braces_span,
would_be_exhaustive_without_guards,
));
}
}
@@ -1153,6 +1166,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
witnesses: Vec<WitnessPat<'p, 'tcx>>,
arms: &[ArmId],
braces_span: Option<Span>,
would_be_exhaustive_without_guards: bool,
) -> ErrorGuaranteed {
let is_empty_match = arms.is_empty();
let non_empty_enum = match scrut_ty.kind() {
@@ -1363,8 +1377,7 @@ fn report_non_exhaustive_match<'p, 'tcx>(
},
);
let all_arms_have_guards = arms.iter().all(|arm_id| thir[*arm_id].guard.is_some());
if !is_empty_match && all_arms_have_guards {
if would_be_exhaustive_without_guards {
err.subdiagnostic(NonExhaustiveMatchAllArmsGuarded);
}
if let Some((span, sugg)) = suggestion {
@@ -569,6 +569,7 @@ note: `Result<!, !>` defined here
= note: not covered
= note: the matched value is of type `Result<!, !>`
= note: `!` is uninhabited but is not being matched by value, so a wildcard `_` is required
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL ~ Err(_) => {},
@@ -569,6 +569,7 @@ note: `Result<!, !>` defined here
= note: not covered
= note: the matched value is of type `Result<!, !>`
= note: `!` is uninhabited but is not being matched by value, so a wildcard `_` is required
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL ~ Err(_) => {},
@@ -5,6 +5,7 @@ LL | match 0u8 {
| ^^^ pattern `128_u8..=u8::MAX` not covered
|
= note: the matched value is of type `u8`
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL ~ 128 ..= 255 if true => {},
@@ -20,6 +20,7 @@ LL | match "world" {
|
= note: the matched value is of type `&str`
= note: `&str` cannot be matched exhaustively, so a wildcard `_` is necessary
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL ~ "hello" => {},
@@ -9,6 +9,7 @@ fn main() {
//~^ ERROR non-exhaustive patterns: `(X::A, Some(X::A))`, `(X::A, Some(X::B))`, `(X::B, Some(X::B))` and 2
//~| NOTE more not covered
//~| NOTE the matched value is of type `(X, Option<X>)`
//~| NOTE match arms with guards don't count towards exhaustivity
(_, None) => false,
(v, Some(w)) if v == w => true,
(X::B, Some(X::C)) => false,
@@ -5,6 +5,7 @@ LL | match (x, y) {
| ^^^^^^ patterns `(X::A, Some(X::A))`, `(X::A, Some(X::B))`, `(X::B, Some(X::B))` and 2 more not covered
|
= note: the matched value is of type `(X, Option<X>)`
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms
|
LL ~ (X::A, Some(X::C)) | (X::C, Some(X::A)) => false,
@@ -1,5 +1,4 @@
fn main() {
match 0 { 1 => () } //~ ERROR non-exhaustive patterns
match 0 { 0 if false => () } //~ ERROR non-exhaustive patterns
//-| NOTE match arms with guards don't count towards exhaustivity
}
@@ -17,7 +17,6 @@ LL | match 0 { 0 if false => () }
| ^ patterns `i32::MIN..=-1_i32` and `1_i32..=i32::MAX` not covered
|
= note: the matched value is of type `i32`
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
|
LL | match 0 { 0 if false => (), i32::MIN..=-1_i32 | 1_i32..=i32::MAX => todo!() }
@@ -44,6 +44,7 @@ note: `Option<Void>` defined here
= note: not covered
= note: the matched value is of type `&Option<Void>`
= note: `Void` is uninhabited but is not being matched by value, so a wildcard `_` is required
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL ~ None => {},
@@ -63,6 +64,7 @@ note: `Option<Void>` defined here
= note: not covered
= note: the matched value is of type `&Option<Void>`
= note: `Void` is uninhabited but is not being matched by value, so a wildcard `_` is required
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL ~ None => {},
@@ -10,6 +10,7 @@ note: `Option<u32>` defined here
|
= note: not covered
= note: the matched value is of type `Option<u32>`
= note: match arms with guards don't count towards exhaustivity
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL ~ None if let y = x => {},