Rollup merge of #122644 - Nadrieril:complexity-tests, r=compiler-errors
pattern analysis: add a custom test harness There are two features of the pattern analysis code that are hard to test: the newly-added pattern complexity limit, and the computation of arm intersections. This PR adds some crate-specific tests for that, including an unmaintainable but pretty macro to help construct patterns. r? `````@compiler-errors`````
This commit is contained in:
commit
8d12621181
11 changed files with 692 additions and 80 deletions
|
@ -819,6 +819,81 @@ impl<Cx: PatCx> Constructor<Cx> {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn fmt_fields(
|
||||
&self,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
ty: &Cx::Ty,
|
||||
mut fields: impl Iterator<Item = impl fmt::Debug>,
|
||||
) -> fmt::Result {
|
||||
let mut first = true;
|
||||
let mut start_or_continue = |s| {
|
||||
if first {
|
||||
first = false;
|
||||
""
|
||||
} else {
|
||||
s
|
||||
}
|
||||
};
|
||||
let mut start_or_comma = || start_or_continue(", ");
|
||||
|
||||
match self {
|
||||
Struct | Variant(_) | UnionField => {
|
||||
Cx::write_variant_name(f, self, ty)?;
|
||||
// Without `cx`, we can't know which field corresponds to which, so we can't
|
||||
// get the names of the fields. Instead we just display everything as a tuple
|
||||
// struct, which should be good enough.
|
||||
write!(f, "(")?;
|
||||
for p in fields {
|
||||
write!(f, "{}{:?}", start_or_comma(), p)?;
|
||||
}
|
||||
write!(f, ")")?;
|
||||
}
|
||||
// Note: given the expansion of `&str` patterns done in `expand_pattern`, we should
|
||||
// be careful to detect strings here. However a string literal pattern will never
|
||||
// be reported as a non-exhaustiveness witness, so we can ignore this issue.
|
||||
Ref => {
|
||||
write!(f, "&{:?}", &fields.next().unwrap())?;
|
||||
}
|
||||
Slice(slice) => {
|
||||
write!(f, "[")?;
|
||||
match slice.kind {
|
||||
SliceKind::FixedLen(_) => {
|
||||
for p in fields {
|
||||
write!(f, "{}{:?}", start_or_comma(), p)?;
|
||||
}
|
||||
}
|
||||
SliceKind::VarLen(prefix_len, _) => {
|
||||
for p in fields.by_ref().take(prefix_len) {
|
||||
write!(f, "{}{:?}", start_or_comma(), p)?;
|
||||
}
|
||||
write!(f, "{}..", start_or_comma())?;
|
||||
for p in fields {
|
||||
write!(f, "{}{:?}", start_or_comma(), p)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
write!(f, "]")?;
|
||||
}
|
||||
Bool(b) => write!(f, "{b}")?,
|
||||
// Best-effort, will render signed ranges incorrectly
|
||||
IntRange(range) => write!(f, "{range:?}")?,
|
||||
F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}")?,
|
||||
F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}")?,
|
||||
Str(value) => write!(f, "{value:?}")?,
|
||||
Opaque(..) => write!(f, "<constant pattern>")?,
|
||||
Or => {
|
||||
for pat in fields {
|
||||
write!(f, "{}{:?}", start_or_continue(" | "), pat)?;
|
||||
}
|
||||
}
|
||||
Never => write!(f, "!")?,
|
||||
Wildcard | Missing | NonExhaustive | Hidden | PrivateUninhabited => {
|
||||
write!(f, "_ : {:?}", ty)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
|
@ -49,6 +49,12 @@ pub mod index {
|
|||
}
|
||||
}
|
||||
|
||||
impl<V> FromIterator<V> for IdxContainer<usize, V> {
|
||||
fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().enumerate().collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IdxSet<T>(pub rustc_hash::FxHashSet<T>);
|
||||
impl<T: Idx> IdxSet<T> {
|
||||
|
@ -120,7 +126,8 @@ pub trait PatCx: Sized + fmt::Debug {
|
|||
/// `DeconstructedPat`. Only invoqued when `pat.ctor()` is `Struct | Variant(_) | UnionField`.
|
||||
fn write_variant_name(
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
pat: &crate::pat::DeconstructedPat<Self>,
|
||||
ctor: &crate::constructor::Constructor<Self>,
|
||||
ty: &Self::Ty,
|
||||
) -> fmt::Result;
|
||||
|
||||
/// Raise a bug.
|
||||
|
|
|
@ -138,81 +138,11 @@ impl<Cx: PatCx> DeconstructedPat<Cx> {
|
|||
/// This is best effort and not good enough for a `Display` impl.
|
||||
impl<Cx: PatCx> fmt::Debug for DeconstructedPat<Cx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let pat = self;
|
||||
let mut first = true;
|
||||
let mut start_or_continue = |s| {
|
||||
if first {
|
||||
first = false;
|
||||
""
|
||||
} else {
|
||||
s
|
||||
}
|
||||
};
|
||||
let mut start_or_comma = || start_or_continue(", ");
|
||||
|
||||
let mut fields: Vec<_> = (0..self.arity).map(|_| PatOrWild::Wild).collect();
|
||||
for ipat in self.iter_fields() {
|
||||
fields[ipat.idx] = PatOrWild::Pat(&ipat.pat);
|
||||
}
|
||||
|
||||
match pat.ctor() {
|
||||
Struct | Variant(_) | UnionField => {
|
||||
Cx::write_variant_name(f, pat)?;
|
||||
// Without `cx`, we can't know which field corresponds to which, so we can't
|
||||
// get the names of the fields. Instead we just display everything as a tuple
|
||||
// struct, which should be good enough.
|
||||
write!(f, "(")?;
|
||||
for p in fields {
|
||||
write!(f, "{}", start_or_comma())?;
|
||||
write!(f, "{p:?}")?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
// Note: given the expansion of `&str` patterns done in `expand_pattern`, we should
|
||||
// be careful to detect strings here. However a string literal pattern will never
|
||||
// be reported as a non-exhaustiveness witness, so we can ignore this issue.
|
||||
Ref => {
|
||||
write!(f, "&{:?}", &fields[0])
|
||||
}
|
||||
Slice(slice) => {
|
||||
write!(f, "[")?;
|
||||
match slice.kind {
|
||||
SliceKind::FixedLen(_) => {
|
||||
for p in fields {
|
||||
write!(f, "{}{:?}", start_or_comma(), p)?;
|
||||
}
|
||||
}
|
||||
SliceKind::VarLen(prefix_len, _) => {
|
||||
for p in &fields[..prefix_len] {
|
||||
write!(f, "{}{:?}", start_or_comma(), p)?;
|
||||
}
|
||||
write!(f, "{}", start_or_comma())?;
|
||||
write!(f, "..")?;
|
||||
for p in &fields[prefix_len..] {
|
||||
write!(f, "{}{:?}", start_or_comma(), p)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
Bool(b) => write!(f, "{b}"),
|
||||
// Best-effort, will render signed ranges incorrectly
|
||||
IntRange(range) => write!(f, "{range:?}"),
|
||||
F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"),
|
||||
F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"),
|
||||
Str(value) => write!(f, "{value:?}"),
|
||||
Opaque(..) => write!(f, "<constant pattern>"),
|
||||
Or => {
|
||||
for pat in fields {
|
||||
write!(f, "{}{:?}", start_or_continue(" | "), pat)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Never => write!(f, "!"),
|
||||
Wildcard | Missing | NonExhaustive | Hidden | PrivateUninhabited => {
|
||||
write!(f, "_ : {:?}", pat.ty())
|
||||
}
|
||||
}
|
||||
self.ctor().fmt_fields(f, self.ty(), fields.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,7 +225,6 @@ impl<'p, Cx: PatCx> fmt::Debug for PatOrWild<'p, Cx> {
|
|||
|
||||
/// Same idea as `DeconstructedPat`, except this is a fictitious pattern built up for diagnostics
|
||||
/// purposes. As such they don't use interning and can be cloned.
|
||||
#[derive(Debug)]
|
||||
pub struct WitnessPat<Cx: PatCx> {
|
||||
ctor: Constructor<Cx>,
|
||||
pub(crate) fields: Vec<WitnessPat<Cx>>,
|
||||
|
@ -353,3 +282,10 @@ impl<Cx: PatCx> WitnessPat<Cx> {
|
|||
self.fields.iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// This is best effort and not good enough for a `Display` impl.
|
||||
impl<Cx: PatCx> fmt::Debug for WitnessPat<Cx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.ctor().fmt_fields(f, self.ty(), self.fields.iter())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -880,13 +880,14 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> {
|
|||
|
||||
fn write_variant_name(
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
pat: &crate::pat::DeconstructedPat<Self>,
|
||||
ctor: &crate::constructor::Constructor<Self>,
|
||||
ty: &Self::Ty,
|
||||
) -> fmt::Result {
|
||||
if let ty::Adt(adt, _) = pat.ty().kind() {
|
||||
if let ty::Adt(adt, _) = ty.kind() {
|
||||
if adt.is_box() {
|
||||
write!(f, "Box")?
|
||||
} else {
|
||||
let variant = adt.variant(Self::variant_index_for_adt(pat.ctor(), *adt));
|
||||
let variant = adt.variant(Self::variant_index_for_adt(ctor, *adt));
|
||||
write!(f, "{}", variant.name)?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1042,7 +1042,7 @@ struct MatrixRow<'p, Cx: PatCx> {
|
|||
is_under_guard: bool,
|
||||
/// When we specialize, we remember which row of the original matrix produced a given row of the
|
||||
/// specialized matrix. When we unspecialize, we use this to propagate usefulness back up the
|
||||
/// callstack.
|
||||
/// callstack. On creation, this stores the index of the original match arm.
|
||||
parent_row: usize,
|
||||
/// False when the matrix is just built. This is set to `true` by
|
||||
/// [`compute_exhaustiveness_and_usefulness`] if the arm is found to be useful.
|
||||
|
@ -1163,10 +1163,10 @@ impl<'p, Cx: PatCx> Matrix<'p, Cx> {
|
|||
place_info: smallvec![place_info],
|
||||
wildcard_row_is_relevant: true,
|
||||
};
|
||||
for (row_id, arm) in arms.iter().enumerate() {
|
||||
for (arm_id, arm) in arms.iter().enumerate() {
|
||||
let v = MatrixRow {
|
||||
pats: PatStack::from_pattern(arm.pat),
|
||||
parent_row: row_id, // dummy, we don't read it
|
||||
parent_row: arm_id,
|
||||
is_under_guard: arm.has_guard,
|
||||
useful: false,
|
||||
intersects: BitSet::new_empty(0), // Initialized in `Matrix::expand_and_push`.
|
||||
|
@ -1738,6 +1738,9 @@ pub struct UsefulnessReport<'p, Cx: PatCx> {
|
|||
/// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of
|
||||
/// exhaustiveness.
|
||||
pub non_exhaustiveness_witnesses: Vec<WitnessPat<Cx>>,
|
||||
/// For each arm, a set of indices of arms above it that have non-empty intersection, i.e. there
|
||||
/// is a value matched by both arms. This may miss real intersections.
|
||||
pub arm_intersections: Vec<BitSet<usize>>,
|
||||
}
|
||||
|
||||
/// Computes whether a match is exhaustive and which of its arms are useful.
|
||||
|
@ -1769,5 +1772,19 @@ pub fn compute_match_usefulness<'p, Cx: PatCx>(
|
|||
})
|
||||
.collect();
|
||||
|
||||
Ok(UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses })
|
||||
let mut arm_intersections: Vec<_> =
|
||||
arms.iter().enumerate().map(|(i, _)| BitSet::new_empty(i)).collect();
|
||||
for row in matrix.rows() {
|
||||
let arm_id = row.parent_row;
|
||||
for intersection in row.intersects.iter() {
|
||||
// Convert the matrix row ids into arm ids (they can differ because we expand or-patterns).
|
||||
let arm_intersection = matrix.rows[intersection].parent_row;
|
||||
// Note: self-intersection can happen with or-patterns.
|
||||
if arm_intersection != arm_id {
|
||||
arm_intersections[arm_id].insert(arm_intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses, arm_intersections })
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue