Skip to content

Commit 01b16b5

Browse files
Rollup merge of rust-lang#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`
2 parents a128516 + d697dd4 commit 01b16b5

File tree

11 files changed

+692
-80
lines changed

11 files changed

+692
-80
lines changed

Cargo.lock

+2
Original file line numberDiff line numberDiff line change
@@ -4440,6 +4440,8 @@ dependencies = [
44404440
"rustc_target",
44414441
"smallvec",
44424442
"tracing",
4443+
"tracing-subscriber",
4444+
"tracing-tree",
44434445
]
44444446

44454447
[[package]]

compiler/rustc_pattern_analysis/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ smallvec = { version = "1.8.1", features = ["union"] }
2222
tracing = "0.1"
2323
# tidy-alphabetical-end
2424

25+
[dev-dependencies]
26+
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "ansi"] }
27+
tracing-tree = "0.2.0"
28+
2529
[features]
2630
default = ["rustc"]
2731
rustc = [

compiler/rustc_pattern_analysis/src/constructor.rs

+75
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,81 @@ impl<Cx: PatCx> Constructor<Cx> {
819819
}
820820
})
821821
}
822+
823+
pub(crate) fn fmt_fields(
824+
&self,
825+
f: &mut fmt::Formatter<'_>,
826+
ty: &Cx::Ty,
827+
mut fields: impl Iterator<Item = impl fmt::Debug>,
828+
) -> fmt::Result {
829+
let mut first = true;
830+
let mut start_or_continue = |s| {
831+
if first {
832+
first = false;
833+
""
834+
} else {
835+
s
836+
}
837+
};
838+
let mut start_or_comma = || start_or_continue(", ");
839+
840+
match self {
841+
Struct | Variant(_) | UnionField => {
842+
Cx::write_variant_name(f, self, ty)?;
843+
// Without `cx`, we can't know which field corresponds to which, so we can't
844+
// get the names of the fields. Instead we just display everything as a tuple
845+
// struct, which should be good enough.
846+
write!(f, "(")?;
847+
for p in fields {
848+
write!(f, "{}{:?}", start_or_comma(), p)?;
849+
}
850+
write!(f, ")")?;
851+
}
852+
// Note: given the expansion of `&str` patterns done in `expand_pattern`, we should
853+
// be careful to detect strings here. However a string literal pattern will never
854+
// be reported as a non-exhaustiveness witness, so we can ignore this issue.
855+
Ref => {
856+
write!(f, "&{:?}", &fields.next().unwrap())?;
857+
}
858+
Slice(slice) => {
859+
write!(f, "[")?;
860+
match slice.kind {
861+
SliceKind::FixedLen(_) => {
862+
for p in fields {
863+
write!(f, "{}{:?}", start_or_comma(), p)?;
864+
}
865+
}
866+
SliceKind::VarLen(prefix_len, _) => {
867+
for p in fields.by_ref().take(prefix_len) {
868+
write!(f, "{}{:?}", start_or_comma(), p)?;
869+
}
870+
write!(f, "{}..", start_or_comma())?;
871+
for p in fields {
872+
write!(f, "{}{:?}", start_or_comma(), p)?;
873+
}
874+
}
875+
}
876+
write!(f, "]")?;
877+
}
878+
Bool(b) => write!(f, "{b}")?,
879+
// Best-effort, will render signed ranges incorrectly
880+
IntRange(range) => write!(f, "{range:?}")?,
881+
F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}")?,
882+
F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}")?,
883+
Str(value) => write!(f, "{value:?}")?,
884+
Opaque(..) => write!(f, "<constant pattern>")?,
885+
Or => {
886+
for pat in fields {
887+
write!(f, "{}{:?}", start_or_continue(" | "), pat)?;
888+
}
889+
}
890+
Never => write!(f, "!")?,
891+
Wildcard | Missing | NonExhaustive | Hidden | PrivateUninhabited => {
892+
write!(f, "_ : {:?}", ty)?
893+
}
894+
}
895+
Ok(())
896+
}
822897
}
823898

824899
#[derive(Debug, Clone, Copy)]

compiler/rustc_pattern_analysis/src/lib.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ pub mod index {
4949
}
5050
}
5151

52+
impl<V> FromIterator<V> for IdxContainer<usize, V> {
53+
fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
54+
Self(iter.into_iter().enumerate().collect())
55+
}
56+
}
57+
5258
#[derive(Debug)]
5359
pub struct IdxSet<T>(pub rustc_hash::FxHashSet<T>);
5460
impl<T: Idx> IdxSet<T> {
@@ -120,7 +126,8 @@ pub trait PatCx: Sized + fmt::Debug {
120126
/// `DeconstructedPat`. Only invoqued when `pat.ctor()` is `Struct | Variant(_) | UnionField`.
121127
fn write_variant_name(
122128
f: &mut fmt::Formatter<'_>,
123-
pat: &crate::pat::DeconstructedPat<Self>,
129+
ctor: &crate::constructor::Constructor<Self>,
130+
ty: &Self::Ty,
124131
) -> fmt::Result;
125132

126133
/// Raise a bug.

compiler/rustc_pattern_analysis/src/pat.rs

+8-72
Original file line numberDiff line numberDiff line change
@@ -138,81 +138,11 @@ impl<Cx: PatCx> DeconstructedPat<Cx> {
138138
/// This is best effort and not good enough for a `Display` impl.
139139
impl<Cx: PatCx> fmt::Debug for DeconstructedPat<Cx> {
140140
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141-
let pat = self;
142-
let mut first = true;
143-
let mut start_or_continue = |s| {
144-
if first {
145-
first = false;
146-
""
147-
} else {
148-
s
149-
}
150-
};
151-
let mut start_or_comma = || start_or_continue(", ");
152-
153141
let mut fields: Vec<_> = (0..self.arity).map(|_| PatOrWild::Wild).collect();
154142
for ipat in self.iter_fields() {
155143
fields[ipat.idx] = PatOrWild::Pat(&ipat.pat);
156144
}
157-
158-
match pat.ctor() {
159-
Struct | Variant(_) | UnionField => {
160-
Cx::write_variant_name(f, pat)?;
161-
// Without `cx`, we can't know which field corresponds to which, so we can't
162-
// get the names of the fields. Instead we just display everything as a tuple
163-
// struct, which should be good enough.
164-
write!(f, "(")?;
165-
for p in fields {
166-
write!(f, "{}", start_or_comma())?;
167-
write!(f, "{p:?}")?;
168-
}
169-
write!(f, ")")
170-
}
171-
// Note: given the expansion of `&str` patterns done in `expand_pattern`, we should
172-
// be careful to detect strings here. However a string literal pattern will never
173-
// be reported as a non-exhaustiveness witness, so we can ignore this issue.
174-
Ref => {
175-
write!(f, "&{:?}", &fields[0])
176-
}
177-
Slice(slice) => {
178-
write!(f, "[")?;
179-
match slice.kind {
180-
SliceKind::FixedLen(_) => {
181-
for p in fields {
182-
write!(f, "{}{:?}", start_or_comma(), p)?;
183-
}
184-
}
185-
SliceKind::VarLen(prefix_len, _) => {
186-
for p in &fields[..prefix_len] {
187-
write!(f, "{}{:?}", start_or_comma(), p)?;
188-
}
189-
write!(f, "{}", start_or_comma())?;
190-
write!(f, "..")?;
191-
for p in &fields[prefix_len..] {
192-
write!(f, "{}{:?}", start_or_comma(), p)?;
193-
}
194-
}
195-
}
196-
write!(f, "]")
197-
}
198-
Bool(b) => write!(f, "{b}"),
199-
// Best-effort, will render signed ranges incorrectly
200-
IntRange(range) => write!(f, "{range:?}"),
201-
F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"),
202-
F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"),
203-
Str(value) => write!(f, "{value:?}"),
204-
Opaque(..) => write!(f, "<constant pattern>"),
205-
Or => {
206-
for pat in fields {
207-
write!(f, "{}{:?}", start_or_continue(" | "), pat)?;
208-
}
209-
Ok(())
210-
}
211-
Never => write!(f, "!"),
212-
Wildcard | Missing | NonExhaustive | Hidden | PrivateUninhabited => {
213-
write!(f, "_ : {:?}", pat.ty())
214-
}
215-
}
145+
self.ctor().fmt_fields(f, self.ty(), fields.into_iter())
216146
}
217147
}
218148

@@ -295,7 +225,6 @@ impl<'p, Cx: PatCx> fmt::Debug for PatOrWild<'p, Cx> {
295225

296226
/// Same idea as `DeconstructedPat`, except this is a fictitious pattern built up for diagnostics
297227
/// purposes. As such they don't use interning and can be cloned.
298-
#[derive(Debug)]
299228
pub struct WitnessPat<Cx: PatCx> {
300229
ctor: Constructor<Cx>,
301230
pub(crate) fields: Vec<WitnessPat<Cx>>,
@@ -353,3 +282,10 @@ impl<Cx: PatCx> WitnessPat<Cx> {
353282
self.fields.iter()
354283
}
355284
}
285+
286+
/// This is best effort and not good enough for a `Display` impl.
287+
impl<Cx: PatCx> fmt::Debug for WitnessPat<Cx> {
288+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289+
self.ctor().fmt_fields(f, self.ty(), self.fields.iter())
290+
}
291+
}

compiler/rustc_pattern_analysis/src/rustc.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -874,13 +874,14 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> {
874874

875875
fn write_variant_name(
876876
f: &mut fmt::Formatter<'_>,
877-
pat: &crate::pat::DeconstructedPat<Self>,
877+
ctor: &crate::constructor::Constructor<Self>,
878+
ty: &Self::Ty,
878879
) -> fmt::Result {
879-
if let ty::Adt(adt, _) = pat.ty().kind() {
880+
if let ty::Adt(adt, _) = ty.kind() {
880881
if adt.is_box() {
881882
write!(f, "Box")?
882883
} else {
883-
let variant = adt.variant(Self::variant_index_for_adt(pat.ctor(), *adt));
884+
let variant = adt.variant(Self::variant_index_for_adt(ctor, *adt));
884885
write!(f, "{}", variant.name)?;
885886
}
886887
}

compiler/rustc_pattern_analysis/src/usefulness.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,7 @@ struct MatrixRow<'p, Cx: PatCx> {
10421042
is_under_guard: bool,
10431043
/// When we specialize, we remember which row of the original matrix produced a given row of the
10441044
/// specialized matrix. When we unspecialize, we use this to propagate usefulness back up the
1045-
/// callstack.
1045+
/// callstack. On creation, this stores the index of the original match arm.
10461046
parent_row: usize,
10471047
/// False when the matrix is just built. This is set to `true` by
10481048
/// [`compute_exhaustiveness_and_usefulness`] if the arm is found to be useful.
@@ -1163,10 +1163,10 @@ impl<'p, Cx: PatCx> Matrix<'p, Cx> {
11631163
place_info: smallvec![place_info],
11641164
wildcard_row_is_relevant: true,
11651165
};
1166-
for (row_id, arm) in arms.iter().enumerate() {
1166+
for (arm_id, arm) in arms.iter().enumerate() {
11671167
let v = MatrixRow {
11681168
pats: PatStack::from_pattern(arm.pat),
1169-
parent_row: row_id, // dummy, we don't read it
1169+
parent_row: arm_id,
11701170
is_under_guard: arm.has_guard,
11711171
useful: false,
11721172
intersects: BitSet::new_empty(0), // Initialized in `Matrix::expand_and_push`.
@@ -1738,6 +1738,9 @@ pub struct UsefulnessReport<'p, Cx: PatCx> {
17381738
/// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of
17391739
/// exhaustiveness.
17401740
pub non_exhaustiveness_witnesses: Vec<WitnessPat<Cx>>,
1741+
/// For each arm, a set of indices of arms above it that have non-empty intersection, i.e. there
1742+
/// is a value matched by both arms. This may miss real intersections.
1743+
pub arm_intersections: Vec<BitSet<usize>>,
17411744
}
17421745

17431746
/// 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>(
17691772
})
17701773
.collect();
17711774

1772-
Ok(UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses })
1775+
let mut arm_intersections: Vec<_> =
1776+
arms.iter().enumerate().map(|(i, _)| BitSet::new_empty(i)).collect();
1777+
for row in matrix.rows() {
1778+
let arm_id = row.parent_row;
1779+
for intersection in row.intersects.iter() {
1780+
// Convert the matrix row ids into arm ids (they can differ because we expand or-patterns).
1781+
let arm_intersection = matrix.rows[intersection].parent_row;
1782+
// Note: self-intersection can happen with or-patterns.
1783+
if arm_intersection != arm_id {
1784+
arm_intersections[arm_id].insert(arm_intersection);
1785+
}
1786+
}
1787+
}
1788+
1789+
Ok(UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses, arm_intersections })
17731790
}

0 commit comments

Comments
 (0)