From b400f22c7b5125a3f35c12ecae1d31251e90fdc5 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Tue, 7 Apr 2026 10:04:24 +0100 Subject: [PATCH 1/2] 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. Previously, this note was shown whenever all arms had guards, even if the patterns themselves were insufficient to cover all valid values of a type. Re-run 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. --- .../src/thir/pattern/check_match.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index baabc1afe3fa..eae798358b11 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -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, )); } } @@ -1154,6 +1167,7 @@ fn report_non_exhaustive_match<'p, 'tcx>( witnesses: Vec>, arms: &[ArmId], braces_span: Option, + would_be_exhaustive_without_guards: bool, ) -> ErrorGuaranteed { let is_empty_match = arms.is_empty(); let non_empty_enum = match scrut_ty.kind() { @@ -1364,8 +1378,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 { From db8ce5fafb54085e9a6c7b511a47b34a086c9397 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Tue, 7 Apr 2026 10:08:49 +0100 Subject: [PATCH 2/2] Update results for tests affected by the change --- tests/ui/pattern/usefulness/empty-types.never_pats.stderr | 1 + tests/ui/pattern/usefulness/empty-types.normal.stderr | 1 + tests/ui/pattern/usefulness/guards.stderr | 1 + tests/ui/pattern/usefulness/issue-30240.stderr | 1 + tests/ui/pattern/usefulness/issue-72377.rs | 1 + tests/ui/pattern/usefulness/issue-72377.stderr | 1 + tests/ui/pattern/usefulness/match-non-exhaustive.rs | 1 - tests/ui/pattern/usefulness/match-non-exhaustive.stderr | 1 - tests/ui/rfcs/rfc-0000-never_patterns/check.stderr | 2 ++ tests/ui/rfcs/rfc-2294-if-let-guard/exhaustive.stderr | 1 + 10 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/ui/pattern/usefulness/empty-types.never_pats.stderr b/tests/ui/pattern/usefulness/empty-types.never_pats.stderr index 046c0d5f6588..68a1bebf6b13 100644 --- a/tests/ui/pattern/usefulness/empty-types.never_pats.stderr +++ b/tests/ui/pattern/usefulness/empty-types.never_pats.stderr @@ -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(_) => {}, diff --git a/tests/ui/pattern/usefulness/empty-types.normal.stderr b/tests/ui/pattern/usefulness/empty-types.normal.stderr index ba158c1176b3..320959534e52 100644 --- a/tests/ui/pattern/usefulness/empty-types.normal.stderr +++ b/tests/ui/pattern/usefulness/empty-types.normal.stderr @@ -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(_) => {}, diff --git a/tests/ui/pattern/usefulness/guards.stderr b/tests/ui/pattern/usefulness/guards.stderr index 82ed2a93c55f..8907fd155f5d 100644 --- a/tests/ui/pattern/usefulness/guards.stderr +++ b/tests/ui/pattern/usefulness/guards.stderr @@ -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 => {}, diff --git a/tests/ui/pattern/usefulness/issue-30240.stderr b/tests/ui/pattern/usefulness/issue-30240.stderr index da8bbdffbf6d..d592d3110adb 100644 --- a/tests/ui/pattern/usefulness/issue-30240.stderr +++ b/tests/ui/pattern/usefulness/issue-30240.stderr @@ -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" => {}, diff --git a/tests/ui/pattern/usefulness/issue-72377.rs b/tests/ui/pattern/usefulness/issue-72377.rs index 782a9963f2e5..61a74fa68f90 100644 --- a/tests/ui/pattern/usefulness/issue-72377.rs +++ b/tests/ui/pattern/usefulness/issue-72377.rs @@ -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)` + //~| 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, diff --git a/tests/ui/pattern/usefulness/issue-72377.stderr b/tests/ui/pattern/usefulness/issue-72377.stderr index 1eaee1bd3521..ec42caa49d3e 100644 --- a/tests/ui/pattern/usefulness/issue-72377.stderr +++ b/tests/ui/pattern/usefulness/issue-72377.stderr @@ -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)` + = 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, diff --git a/tests/ui/pattern/usefulness/match-non-exhaustive.rs b/tests/ui/pattern/usefulness/match-non-exhaustive.rs index 62c185d04b35..3b210a115d21 100644 --- a/tests/ui/pattern/usefulness/match-non-exhaustive.rs +++ b/tests/ui/pattern/usefulness/match-non-exhaustive.rs @@ -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 } diff --git a/tests/ui/pattern/usefulness/match-non-exhaustive.stderr b/tests/ui/pattern/usefulness/match-non-exhaustive.stderr index 1a0cc58f35df..f226bdf80646 100644 --- a/tests/ui/pattern/usefulness/match-non-exhaustive.stderr +++ b/tests/ui/pattern/usefulness/match-non-exhaustive.stderr @@ -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!() } diff --git a/tests/ui/rfcs/rfc-0000-never_patterns/check.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/check.stderr index 4622ea59b547..81e0df15a6e1 100644 --- a/tests/ui/rfcs/rfc-0000-never_patterns/check.stderr +++ b/tests/ui/rfcs/rfc-0000-never_patterns/check.stderr @@ -44,6 +44,7 @@ note: `Option` defined here = note: not covered = note: the matched value is of type `&Option` = 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` defined here = note: not covered = note: the matched value is of type `&Option` = 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 => {}, diff --git a/tests/ui/rfcs/rfc-2294-if-let-guard/exhaustive.stderr b/tests/ui/rfcs/rfc-2294-if-let-guard/exhaustive.stderr index ca92aa491d70..4ff7194542bf 100644 --- a/tests/ui/rfcs/rfc-2294-if-let-guard/exhaustive.stderr +++ b/tests/ui/rfcs/rfc-2294-if-let-guard/exhaustive.stderr @@ -10,6 +10,7 @@ note: `Option` defined here | = note: not covered = note: the matched value is of type `Option` + = 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 => {},