From 710add58e2063616770ec8893e41b3179ad23d31 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 21 Jul 2024 15:17:29 +0200 Subject: [PATCH 1/2] Tweak `collect_non_exhaustive_tys` --- .../src/thir/pattern/check_match.rs | 32 +++++++++---------- compiler/rustc_pattern_analysis/src/rustc.rs | 8 ++++- 2 files changed, 23 insertions(+), 17 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 70065b5a2c3..38f425581c1 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -16,8 +16,8 @@ use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; use rustc_pattern_analysis::errors::Uncovered; use rustc_pattern_analysis::rustc::{ - Constructor, DeconstructedPat, MatchArm, RustcPatCtxt as PatCtxt, Usefulness, UsefulnessReport, - WitnessPat, + Constructor, DeconstructedPat, MatchArm, RevealedTy, RustcPatCtxt as PatCtxt, Usefulness, + UsefulnessReport, WitnessPat, }; use rustc_session::lint::builtin::{ BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS, @@ -998,26 +998,26 @@ fn report_non_exhaustive_match<'p, 'tcx>( err.note(format!("the matched value is of type `{}`", scrut_ty)); if !is_empty_match { - let mut non_exhaustive_tys = FxIndexSet::default(); + let mut special_tys = FxIndexSet::default(); // Look at the first witness. - collect_non_exhaustive_tys(cx, &witnesses[0], &mut non_exhaustive_tys); + collect_special_tys(cx, &witnesses[0], &mut special_tys); - for ty in non_exhaustive_tys { + for ty in special_tys { if ty.is_ptr_sized_integral() { - if ty == cx.tcx.types.usize { + if ty.inner() == cx.tcx.types.usize { err.note(format!( "`{ty}` does not have a fixed maximum value, so half-open ranges are necessary to match \ exhaustively", )); - } else if ty == cx.tcx.types.isize { + } else if ty.inner() == cx.tcx.types.isize { err.note(format!( "`{ty}` does not have fixed minimum and maximum values, so half-open ranges are necessary to match \ exhaustively", )); } - } else if ty == cx.tcx.types.str_ { + } else if ty.inner() == cx.tcx.types.str_ { err.note("`&str` cannot be matched exhaustively, so a wildcard `_` is necessary"); - } else if cx.is_foreign_non_exhaustive_enum(cx.reveal_opaque_ty(ty)) { + } else if cx.is_foreign_non_exhaustive_enum(ty) { err.note(format!("`{ty}` is marked as non-exhaustive, so a wildcard `_` is necessary to match exhaustively")); } } @@ -1168,22 +1168,22 @@ fn joined_uncovered_patterns<'p, 'tcx>( } } -fn collect_non_exhaustive_tys<'tcx>( +/// Collect types that require specific explanations when they show up in witnesses. +fn collect_special_tys<'tcx>( cx: &PatCtxt<'_, 'tcx>, pat: &WitnessPat<'_, 'tcx>, - non_exhaustive_tys: &mut FxIndexSet>, + special_tys: &mut FxIndexSet>, ) { - if matches!(pat.ctor(), Constructor::NonExhaustive) { - non_exhaustive_tys.insert(pat.ty().inner()); + if matches!(pat.ctor(), Constructor::NonExhaustive | Constructor::Never) { + special_tys.insert(*pat.ty()); } if let Constructor::IntRange(range) = pat.ctor() { if cx.is_range_beyond_boundaries(range, *pat.ty()) { // The range denotes the values before `isize::MIN` or the values after `usize::MAX`/`isize::MAX`. - non_exhaustive_tys.insert(pat.ty().inner()); + special_tys.insert(*pat.ty()); } } - pat.iter_fields() - .for_each(|field_pat| collect_non_exhaustive_tys(cx, field_pat, non_exhaustive_tys)) + pat.iter_fields().for_each(|field_pat| collect_special_tys(cx, field_pat, special_tys)) } fn report_adt_defined_here<'tcx>( diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs index d17ee8bff50..6ef2d69273e 100644 --- a/compiler/rustc_pattern_analysis/src/rustc.rs +++ b/compiler/rustc_pattern_analysis/src/rustc.rs @@ -40,9 +40,15 @@ pub type WitnessPat<'p, 'tcx> = crate::pat::WitnessPat>; /// /// Use `.inner()` or deref to get to the `Ty<'tcx>`. #[repr(transparent)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct RevealedTy<'tcx>(Ty<'tcx>); +impl<'tcx> fmt::Display for RevealedTy<'tcx> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(fmt) + } +} + impl<'tcx> fmt::Debug for RevealedTy<'tcx> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(fmt) From 8a49d83db730f3dfdd7f0c04df823549bf571b33 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 21 Jul 2024 15:18:10 +0200 Subject: [PATCH 2/2] Explain why we require `_` for empty patterns --- compiler/rustc_mir_build/src/thir/pattern/check_match.rs | 4 ++++ .../pattern/usefulness/empty-types.min_exh_pats.stderr | 9 +++++++++ .../slice_of_empty.min_exhaustive_patterns.stderr | 1 + 3 files changed, 14 insertions(+) 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 38f425581c1..95799cec94b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -1019,6 +1019,10 @@ fn report_non_exhaustive_match<'p, 'tcx>( err.note("`&str` cannot be matched exhaustively, so a wildcard `_` is necessary"); } else if cx.is_foreign_non_exhaustive_enum(ty) { err.note(format!("`{ty}` is marked as non-exhaustive, so a wildcard `_` is necessary to match exhaustively")); + } else if cx.is_uninhabited(ty.inner()) && cx.tcx.features().min_exhaustive_patterns { + // The type is uninhabited yet there is a witness: we must be in the `MaybeInvalid` + // case. + err.note(format!("`{ty}` is uninhabited but is not being matched by value, so a wildcard `_` is required")); } } } diff --git a/tests/ui/pattern/usefulness/empty-types.min_exh_pats.stderr b/tests/ui/pattern/usefulness/empty-types.min_exh_pats.stderr index 6e50dfe6a26..9b57c895eea 100644 --- a/tests/ui/pattern/usefulness/empty-types.min_exh_pats.stderr +++ b/tests/ui/pattern/usefulness/empty-types.min_exh_pats.stderr @@ -204,6 +204,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 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 => {}, @@ -349,6 +350,7 @@ LL | match slice_never { | ^^^^^^^^^^^ pattern `&[_, ..]` not covered | = note: the matched value is of type `&[!]` + = note: `!` is uninhabited but is not being matched by value, so a wildcard `_` is required 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 ~ [] => {}, @@ -484,6 +486,7 @@ note: `Option` defined here | = note: not covered = note: the matched value is of type `&Option` + = note: `!` is uninhabited but is not being matched by value, so a wildcard `_` is required 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 => {}, @@ -502,6 +505,7 @@ note: `Option` defined here | = note: not covered = note: the matched value is of type `Option` + = note: `!` is uninhabited but is not being matched by value, so a wildcard `_` is required 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 => {}, @@ -520,6 +524,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 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 ~ Ok(_) => {}, @@ -538,6 +543,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 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 ~ Ok(_a) => {}, @@ -589,6 +595,7 @@ LL | match ref_never { | ^^^^^^^^^ pattern `&_` not covered | = note: the matched value is of type `&!` + = note: `!` is uninhabited but is not being matched by value, so a wildcard `_` is required = note: references are always considered inhabited = 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 @@ -609,6 +616,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 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(_) => {}, @@ -627,6 +635,7 @@ note: `Option>` defined here | = note: not covered = note: the matched value is of type `Option>` + = note: `Result` is uninhabited but is not being matched by value, so a wildcard `_` is required 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/pattern/usefulness/slice_of_empty.min_exhaustive_patterns.stderr b/tests/ui/pattern/usefulness/slice_of_empty.min_exhaustive_patterns.stderr index a1239466c9c..f24ce154d14 100644 --- a/tests/ui/pattern/usefulness/slice_of_empty.min_exhaustive_patterns.stderr +++ b/tests/ui/pattern/usefulness/slice_of_empty.min_exhaustive_patterns.stderr @@ -5,6 +5,7 @@ LL | match nevers { | ^^^^^^ pattern `&[_, ..]` not covered | = note: the matched value is of type `&[!]` + = note: `!` is uninhabited but is not being matched by value, so a wildcard `_` is required 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 ~ &[] => (),