pattern_analysis: add option to get a full set of witnesses

This commit is contained in:
Nadrieril
2025-07-19 11:28:31 +02:00
parent 81af9d4569
commit 2bb00741d4
7 changed files with 116 additions and 16 deletions

View File

@@ -950,9 +950,7 @@ impl<Cx: PatCx> Constructor<Cx> {
}
}
Never => write!(f, "!")?,
Wildcard | Missing | NonExhaustive | Hidden | PrivateUninhabited => {
write!(f, "_ : {:?}", ty)?
}
Wildcard | Missing | NonExhaustive | Hidden | PrivateUninhabited => write!(f, "_")?,
}
Ok(())
}

View File

@@ -57,6 +57,13 @@ pub trait PatCx: Sized + fmt::Debug {
fn is_exhaustive_patterns_feature_on(&self) -> bool;
/// Whether to ensure the non-exhaustiveness witnesses we report for a complete set. This is
/// `false` by default to avoid some exponential blowup cases such as
/// <https://github.com/rust-lang/rust/issues/118437>.
fn exhaustive_witnesses(&self) -> bool {
false
}
/// The number of fields for this constructor.
fn ctor_arity(&self, ctor: &Constructor<Self>, ty: &Self::Ty) -> usize;

View File

@@ -1747,7 +1747,9 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: PatCx>(
// `ctor` is *irrelevant* if there's another constructor in `split_ctors` that matches
// strictly fewer rows. In that case we can sometimes skip it. See the top of the file for
// details.
let ctor_is_relevant = matches!(ctor, Constructor::Missing) || missing_ctors.is_empty();
let ctor_is_relevant = matches!(ctor, Constructor::Missing)
|| missing_ctors.is_empty()
|| mcx.tycx.exhaustive_witnesses();
let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor, ctor_is_relevant)?;
let mut witnesses = ensure_sufficient_stack(|| {
compute_exhaustiveness_and_usefulness(mcx, &mut spec_matrix)

View File

@@ -126,10 +126,11 @@ pub(super) fn compute_match_usefulness<'p>(
ty: Ty,
scrut_validity: PlaceValidity,
complexity_limit: usize,
exhaustive_witnesses: bool,
) -> Result<UsefulnessReport<'p, Cx>, ()> {
init_tracing();
rustc_pattern_analysis::usefulness::compute_match_usefulness(
&Cx,
&Cx { exhaustive_witnesses },
arms,
ty,
scrut_validity,
@@ -138,7 +139,9 @@ pub(super) fn compute_match_usefulness<'p>(
}
#[derive(Debug)]
pub(super) struct Cx;
pub(super) struct Cx {
exhaustive_witnesses: bool,
}
/// The context for pattern analysis. Forwards anything interesting to `Ty` methods.
impl PatCx for Cx {
@@ -153,6 +156,10 @@ impl PatCx for Cx {
false
}
fn exhaustive_witnesses(&self) -> bool {
self.exhaustive_witnesses
}
fn ctor_arity(&self, ctor: &Constructor<Self>, ty: &Self::Ty) -> usize {
ty.sub_tys(ctor).len()
}
@@ -219,16 +226,18 @@ macro_rules! pats {
// Entrypoint
// Parse `type; ..`
($ty:expr; $($rest:tt)*) => {{
#[allow(unused_imports)]
#[allow(unused)]
use rustc_pattern_analysis::{
constructor::{Constructor, IntRange, MaybeInfiniteInt, RangeEnd},
pat::DeconstructedPat,
pat::{DeconstructedPat, IndexedPat},
};
let ty = $ty;
// The heart of the macro is designed to push `IndexedPat`s into a `Vec`, so we work around
// that.
#[allow(unused)]
let sub_tys = ::std::iter::repeat(&ty);
let mut vec = Vec::new();
#[allow(unused)]
let mut vec: Vec<IndexedPat<_>> = Vec::new();
pats!(@ctor(vec:vec, sub_tys:sub_tys, idx:0) $($rest)*);
vec.into_iter().map(|ipat| ipat.pat).collect::<Vec<_>>()
}};
@@ -263,6 +272,8 @@ macro_rules! pats {
let ctor = Constructor::Wildcard;
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
// Nothing
(@ctor($($args:tt)*)) => {};
// Integers and int ranges
(@ctor($($args:tt)*) $($start:literal)?..$end:literal $($rest:tt)*) => {{

View File

@@ -16,7 +16,7 @@ fn check(patterns: &[DeconstructedPat<Cx>], complexity_limit: usize) -> Result<(
let ty = *patterns[0].ty();
let arms: Vec<_> =
patterns.iter().map(|pat| MatchArm { pat, has_guard: false, arm_data: () }).collect();
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, complexity_limit)
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, complexity_limit, false)
.map(|_report| ())
}

View File

@@ -11,16 +11,30 @@ use rustc_pattern_analysis::usefulness::PlaceValidity;
mod common;
/// Analyze a match made of these patterns.
fn check(patterns: Vec<DeconstructedPat<Cx>>) -> Vec<WitnessPat<Cx>> {
let ty = *patterns[0].ty();
fn run(
ty: Ty,
patterns: Vec<DeconstructedPat<Cx>>,
exhaustive_witnesses: bool,
) -> Vec<WitnessPat<Cx>> {
let arms: Vec<_> =
patterns.iter().map(|pat| MatchArm { pat, has_guard: false, arm_data: () }).collect();
let report =
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, usize::MAX)
.unwrap();
let report = compute_match_usefulness(
arms.as_slice(),
ty,
PlaceValidity::ValidOnly,
usize::MAX,
exhaustive_witnesses,
)
.unwrap();
report.non_exhaustiveness_witnesses
}
/// Analyze a match made of these patterns. Panics if there are no patterns
fn check(patterns: Vec<DeconstructedPat<Cx>>) -> Vec<WitnessPat<Cx>> {
let ty = *patterns[0].ty();
run(ty, patterns, true)
}
#[track_caller]
fn assert_exhaustive(patterns: Vec<DeconstructedPat<Cx>>) {
let witnesses = check(patterns);
@@ -35,6 +49,26 @@ fn assert_non_exhaustive(patterns: Vec<DeconstructedPat<Cx>>) {
assert!(!witnesses.is_empty())
}
use WhichWitnesses::*;
enum WhichWitnesses {
AllOfThem,
OnlySome,
}
#[track_caller]
/// We take the type as input to support empty matches.
fn assert_witnesses(
which: WhichWitnesses,
ty: Ty,
patterns: Vec<DeconstructedPat<Cx>>,
expected: Vec<&str>,
) {
let exhaustive_wit = matches!(which, AllOfThem);
let witnesses = run(ty, patterns, exhaustive_wit);
let witnesses: Vec<_> = witnesses.iter().map(|w| format!("{w:?}")).collect();
assert_eq!(witnesses, expected)
}
#[test]
fn test_int_ranges() {
let ty = Ty::U8;
@@ -59,6 +93,8 @@ fn test_int_ranges() {
#[test]
fn test_nested() {
// enum E { A(bool), B(bool) }
// ty = (E, E)
let ty = Ty::BigStruct { arity: 2, ty: &Ty::BigEnum { arity: 2, ty: &Ty::Bool } };
assert_non_exhaustive(pats!(ty;
Struct(Variant.0, _),
@@ -78,6 +114,52 @@ fn test_nested() {
));
}
#[test]
fn test_witnesses() {
// TY = Option<bool>
const TY: Ty = Ty::Enum(&[Ty::Bool, Ty::Tuple(&[])]);
// ty = (Option<bool>, Option<bool>)
let ty = Ty::Tuple(&[TY, TY]);
assert_witnesses(AllOfThem, ty, vec![], vec!["(_, _)"]);
assert_witnesses(
OnlySome,
ty,
pats!(ty;
(Variant.0(false), Variant.0(false)),
),
vec!["(Enum::Variant1(_), _)"],
);
assert_witnesses(
AllOfThem,
ty,
pats!(ty;
(Variant.0(false), Variant.0(false)),
),
vec![
"(Enum::Variant0(false), Enum::Variant0(true))",
"(Enum::Variant0(false), Enum::Variant1(_))",
"(Enum::Variant0(true), _)",
"(Enum::Variant1(_), _)",
],
);
assert_witnesses(
OnlySome,
ty,
pats!(ty;
(_, Variant.0(false)),
),
vec!["(_, Enum::Variant1(_))"],
);
assert_witnesses(
AllOfThem,
ty,
pats!(ty;
(_, Variant.0(false)),
),
vec!["(_, Enum::Variant0(true))", "(_, Enum::Variant1(_))"],
);
}
#[test]
fn test_empty() {
// `TY = Result<bool, !>`

View File

@@ -16,7 +16,7 @@ fn check(patterns: Vec<DeconstructedPat<Cx>>) -> Vec<Vec<usize>> {
let arms: Vec<_> =
patterns.iter().map(|pat| MatchArm { pat, has_guard: false, arm_data: () }).collect();
let report =
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, usize::MAX)
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, usize::MAX, false)
.unwrap();
report.arm_intersections.into_iter().map(|bitset| bitset.iter().collect()).collect()
}