Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pattern analysis: add a custom test harness #122644

Merged
merged 3 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4440,6 +4440,8 @@ dependencies = [
"rustc_target",
"smallvec",
"tracing",
"tracing-subscriber",
"tracing-tree",
]

[[package]]
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_pattern_analysis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ smallvec = { version = "1.8.1", features = ["union"] }
tracing = "0.1"
# tidy-alphabetical-end

[dev-dependencies]
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "ansi"] }
tracing-tree = "0.2.0"

[features]
default = ["rustc"]
rustc = [
Expand Down
75 changes: 75 additions & 0 deletions compiler/rustc_pattern_analysis/src/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
9 changes: 8 additions & 1 deletion compiler/rustc_pattern_analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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.
Expand Down
80 changes: 8 additions & 72 deletions compiler/rustc_pattern_analysis/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}

Expand Down Expand Up @@ -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>>,
Expand Down Expand Up @@ -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())
}
}
7 changes: 4 additions & 3 deletions compiler/rustc_pattern_analysis/src/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -874,13 +874,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)?;
}
}
Expand Down
25 changes: 21 additions & 4 deletions compiler/rustc_pattern_analysis/src/usefulness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Loading