Skip to content

Commit 3ce2ac6

Browse files
committed
Auto merge of #143541 - Nadrieril:lint-overlapping-or-pat-under-guard, r=<try>
[WIP] Lint self-overlapping or-patterns under guard This adds an error on or-patterns under match guard that may cause the guard to be run more than once, such as `true | true if f()`. This is for crater purposes. r? ghost
2 parents 1b0bc59 + c0fda28 commit 3ce2ac6

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

compiler/rustc_pattern_analysis/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ pub trait PatCx: Sized + fmt::Debug {
107107
_gapped_with: &[&DeconstructedPat<Self>],
108108
) {
109109
}
110+
111+
/// Lint that an or-pattern will cause a guard to be tried several times because there's a value
112+
/// that matches several of the or-alternatives.
113+
/// The default implementation does nothing.
114+
fn lint_overlapping_alternatives_under_guard(
115+
&self,
116+
_pat1: &DeconstructedPat<Self>,
117+
_pat2: &DeconstructedPat<Self>,
118+
) {
119+
}
110120
}
111121

112122
/// The arm of a match expression.

compiler/rustc_pattern_analysis/src/rustc.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,21 @@ impl<'p, 'tcx: 'p> PatCx for RustcPatCtxt<'p, 'tcx> {
10271027
);
10281028
}
10291029
}
1030+
1031+
fn lint_overlapping_alternatives_under_guard(
1032+
&self,
1033+
pat1: &crate::pat::DeconstructedPat<Self>,
1034+
pat2: &crate::pat::DeconstructedPat<Self>,
1035+
) {
1036+
self.tcx
1037+
.dcx()
1038+
.struct_span_err(
1039+
pat1.data().span,
1040+
format!("pattern overlaps with or-alternative under a guard"),
1041+
)
1042+
.with_span_label(pat2.data().span, "overlaps with")
1043+
.emit();
1044+
}
10301045
}
10311046

10321047
/// Recursively expand this pattern into its subpatterns. Only useful for or-patterns.

compiler/rustc_pattern_analysis/src/usefulness.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1105,14 +1105,20 @@ impl<'p, Cx: PatCx> fmt::Debug for PatStack<'p, Cx> {
11051105
/// A row of the matrix.
11061106
#[derive(Clone)]
11071107
struct MatrixRow<'p, Cx: PatCx> {
1108-
// The patterns in the row.
1108+
/// The patterns in the row.
11091109
pats: PatStack<'p, Cx>,
1110+
/// A (sub)pattern this row comes from. When expanding or-patterns, this tracks the last
1111+
/// alternative expanded, e.g. in `(0|1, 2|3)` we'd keep `3` for the last row. Used only for
1112+
/// diagnostics.
1113+
origin: &'p DeconstructedPat<Cx>,
11101114
/// Whether the original arm had a guard. This is inherited when specializing.
11111115
is_under_guard: bool,
11121116
/// When we specialize, we remember which row of the original matrix produced a given row of the
11131117
/// specialized matrix. When we unspecialize, we use this to propagate usefulness back up the
11141118
/// callstack. On creation, this stores the index of the original match arm.
11151119
parent_row: usize,
1120+
/// Remember the match arm this came from.
1121+
arm_id: usize,
11161122
/// False when the matrix is just built. This is set to `true` by
11171123
/// [`compute_exhaustiveness_and_usefulness`] if the arm is found to be useful.
11181124
/// This is reset to `false` when specializing.
@@ -1142,7 +1148,9 @@ impl<'p, Cx: PatCx> MatrixRow<'p, Cx> {
11421148
fn new(arm: &MatchArm<'p, Cx>, arm_id: usize) -> Self {
11431149
MatrixRow {
11441150
pats: PatStack::from_pattern(arm.pat),
1151+
origin: arm.pat,
11451152
parent_row: arm_id,
1153+
arm_id,
11461154
is_under_guard: arm.has_guard,
11471155
useful: false,
11481156
intersects_at_least: DenseBitSet::new_empty(0), // Initialized in `Matrix::push`.
@@ -1167,8 +1175,10 @@ impl<'p, Cx: PatCx> MatrixRow<'p, Cx> {
11671175
fn expand_or_pat(&self, parent_row: usize) -> impl Iterator<Item = MatrixRow<'p, Cx>> {
11681176
let is_or_pat = self.pats.head().is_or_pat();
11691177
self.pats.expand_or_pat().map(move |patstack| MatrixRow {
1178+
origin: if is_or_pat { patstack.head().as_pat().unwrap() } else { self.origin },
11701179
pats: patstack,
11711180
parent_row,
1181+
arm_id: self.arm_id,
11721182
is_under_guard: self.is_under_guard,
11731183
useful: false,
11741184
intersects_at_least: DenseBitSet::new_empty(0), // Initialized in `Matrix::push`.
@@ -1188,7 +1198,9 @@ impl<'p, Cx: PatCx> MatrixRow<'p, Cx> {
11881198
) -> Result<MatrixRow<'p, Cx>, Cx::Error> {
11891199
Ok(MatrixRow {
11901200
pats: self.pats.pop_head_constructor(cx, ctor, ctor_arity, ctor_is_relevant)?,
1201+
origin: self.origin,
11911202
parent_row,
1203+
arm_id: self.arm_id,
11921204
is_under_guard: self.is_under_guard,
11931205
useful: false,
11941206
intersects_at_least: DenseBitSet::new_empty(0), // Initialized in `Matrix::push`.
@@ -1724,6 +1736,11 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: PatCx>(
17241736
// The next rows stays useful if this one is under a guard.
17251737
useful &= row.is_under_guard;
17261738
}
1739+
for (row1, row2) in matrix.rows().zip(matrix.rows().skip(1)) {
1740+
if row1.arm_id == row2.arm_id && row1.is_under_guard {
1741+
mcx.tycx.lint_overlapping_alternatives_under_guard(row1.origin, row2.origin);
1742+
}
1743+
}
17271744
return if useful && matrix.wildcard_row_is_relevant {
17281745
// The wildcard row is useful; the match is non-exhaustive.
17291746
Ok(WitnessMatrix::unit_witness())

0 commit comments

Comments
 (0)