Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit c79e9b4

Browse files
committed
Lint overlapping ranges as a separate pass
1 parent 57a6f8a commit c79e9b4

File tree

4 files changed

+168
-104
lines changed

4 files changed

+168
-104
lines changed

compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs

Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,20 @@ use smallvec::{smallvec, SmallVec};
5353
use rustc_apfloat::ieee::{DoubleS, IeeeFloat, SingleS};
5454
use rustc_data_structures::captures::Captures;
5555
use rustc_data_structures::fx::FxHashSet;
56-
use rustc_hir::{HirId, RangeEnd};
56+
use rustc_hir::RangeEnd;
5757
use rustc_index::Idx;
5858
use rustc_middle::middle::stability::EvalResult;
5959
use rustc_middle::mir;
6060
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange};
6161
use rustc_middle::ty::layout::IntegerExt;
6262
use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
63-
use rustc_session::lint;
6463
use rustc_span::{Span, DUMMY_SP};
6564
use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};
6665

6766
use self::Constructor::*;
6867
use self::SliceKind::*;
6968

7069
use super::usefulness::{MatchCheckCtxt, PatCtxt};
71-
use crate::errors::{Overlap, OverlappingRangeEndpoints};
7270

7371
/// Recursively expand this pattern into its subpatterns. Only useful for or-patterns.
7472
fn expand_or_pat<'p, 'tcx>(pat: &'p Pat<'tcx>) -> Vec<&'p Pat<'tcx>> {
@@ -111,15 +109,15 @@ pub(crate) struct IntRange {
111109

112110
impl IntRange {
113111
#[inline]
114-
fn is_integral(ty: Ty<'_>) -> bool {
112+
pub(super) fn is_integral(ty: Ty<'_>) -> bool {
115113
matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_) | ty::Bool)
116114
}
117115

118-
fn is_singleton(&self) -> bool {
116+
pub(super) fn is_singleton(&self) -> bool {
119117
self.range.start() == self.range.end()
120118
}
121119

122-
fn boundaries(&self) -> (u128, u128) {
120+
pub(super) fn boundaries(&self) -> (u128, u128) {
123121
(*self.range.start(), *self.range.end())
124122
}
125123

@@ -177,23 +175,6 @@ impl IntRange {
177175
}
178176
}
179177

180-
fn suspicious_intersection(&self, other: &Self) -> bool {
181-
// `false` in the following cases:
182-
// 1 ---- // 1 ---------- // 1 ---- // 1 ----
183-
// 2 ---------- // 2 ---- // 2 ---- // 2 ----
184-
//
185-
// The following are currently `false`, but could be `true` in the future (#64007):
186-
// 1 --------- // 1 ---------
187-
// 2 ---------- // 2 ----------
188-
//
189-
// `true` in the following cases:
190-
// 1 ------- // 1 -------
191-
// 2 -------- // 2 -------
192-
let (lo, hi) = self.boundaries();
193-
let (other_lo, other_hi) = other.boundaries();
194-
(lo == other_hi || hi == other_lo) && !self.is_singleton() && !other.is_singleton()
195-
}
196-
197178
/// Partition a range of integers into disjoint subranges. This does constructor splitting for
198179
/// integer ranges as explained at the top of the file.
199180
///
@@ -293,7 +274,7 @@ impl IntRange {
293274
}
294275

295276
/// Only used for displaying the range.
296-
fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> {
277+
pub(super) fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> {
297278
let (lo, hi) = self.boundaries();
298279

299280
let bias = IntRange::signed_bias(tcx, ty);
@@ -315,51 +296,6 @@ impl IntRange {
315296

316297
Pat { ty, span: DUMMY_SP, kind }
317298
}
318-
319-
/// Lint on likely incorrect range patterns (#63987)
320-
pub(super) fn lint_overlapping_range_endpoints<'a, 'p: 'a, 'tcx: 'a>(
321-
&self,
322-
pcx: &PatCtxt<'_, 'p, 'tcx>,
323-
pats: impl Iterator<Item = &'a DeconstructedPat<'p, 'tcx>>,
324-
column_count: usize,
325-
lint_root: HirId,
326-
) {
327-
if self.is_singleton() {
328-
return;
329-
}
330-
331-
if column_count != 1 {
332-
// FIXME: for now, only check for overlapping ranges on simple range
333-
// patterns. Otherwise with the current logic the following is detected
334-
// as overlapping:
335-
// ```
336-
// match (0u8, true) {
337-
// (0 ..= 125, false) => {}
338-
// (125 ..= 255, true) => {}
339-
// _ => {}
340-
// }
341-
// ```
342-
return;
343-
}
344-
345-
let overlap: Vec<_> = pats
346-
.filter_map(|pat| Some((pat.ctor().as_int_range()?, pat.span())))
347-
.filter(|(range, _)| self.suspicious_intersection(range))
348-
.map(|(range, span)| Overlap {
349-
range: self.intersection(&range).unwrap().to_pat(pcx.cx.tcx, pcx.ty),
350-
span,
351-
})
352-
.collect();
353-
354-
if !overlap.is_empty() {
355-
pcx.cx.tcx.emit_spanned_lint(
356-
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
357-
lint_root,
358-
pcx.span,
359-
OverlappingRangeEndpoints { overlap, range: pcx.span },
360-
);
361-
}
362-
}
363299
}
364300

365301
/// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and
@@ -644,7 +580,7 @@ impl<'tcx> Constructor<'tcx> {
644580
_ => None,
645581
}
646582
}
647-
fn as_int_range(&self) -> Option<&IntRange> {
583+
pub(super) fn as_int_range(&self) -> Option<&IntRange> {
648584
match self {
649585
IntRange(range) => Some(range),
650586
_ => None,

compiler/rustc_mir_build/src/thir/pattern/usefulness.rs

Lines changed: 104 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,8 @@
307307
308308
use self::ArmType::*;
309309
use self::Usefulness::*;
310-
use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, WitnessPat};
311-
use crate::errors::{NonExhaustiveOmittedPattern, Uncovered};
310+
use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, IntRange, WitnessPat};
311+
use crate::errors::{NonExhaustiveOmittedPattern, Overlap, OverlappingRangeEndpoints, Uncovered};
312312

313313
use rustc_data_structures::captures::Captures;
314314

@@ -317,6 +317,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
317317
use rustc_hir::def_id::DefId;
318318
use rustc_hir::HirId;
319319
use rustc_middle::ty::{self, Ty, TyCtxt};
320+
use rustc_session::lint;
320321
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
321322
use rustc_span::{Span, DUMMY_SP};
322323

@@ -473,11 +474,6 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> {
473474
Matrix { patterns: vec![] }
474475
}
475476

476-
/// Number of columns of this matrix. `None` is the matrix is empty.
477-
pub(super) fn column_count(&self) -> Option<usize> {
478-
self.patterns.get(0).map(|r| r.len())
479-
}
480-
481477
/// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively
482478
/// expands it.
483479
fn push(&mut self, row: PatStack<'p, 'tcx>) {
@@ -870,15 +866,6 @@ fn is_useful<'p, 'tcx>(
870866

871867
let v_ctor = v.head().ctor();
872868
debug!(?v_ctor);
873-
if let Constructor::IntRange(ctor_range) = &v_ctor {
874-
// Lint on likely incorrect range patterns (#63987)
875-
ctor_range.lint_overlapping_range_endpoints(
876-
pcx,
877-
matrix.heads(),
878-
matrix.column_count().unwrap_or(0),
879-
lint_root,
880-
)
881-
}
882869
// We split the head constructor of `v`.
883870
let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
884871
// For each constructor, we compute whether there's a value that starts with it that would
@@ -924,7 +911,8 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
924911
let ty = column[0].ty();
925912
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
926913

927-
let set = ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, column.iter().map(|p| p.ctor()));
914+
let column_ctors = column.iter().map(|p| p.ctor());
915+
let set = ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, column_ctors);
928916
if set.present.is_empty() {
929917
// We can't consistently handle the case where no constructors are present (since this would
930918
// require digging deep through any type in case there's a non_exhaustive enum somewhere),
@@ -986,6 +974,102 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
986974
witnesses
987975
}
988976

977+
/// Traverse the patterns to warn the user about ranges that overlap on their endpoints.
978+
/// This traverses patterns column-by-column, where a column is the intuitive notion of "subpatterns
979+
/// that inspect the same subvalue". Despite similarities with `is_useful`, this traversal is
980+
/// different. Notably this is linear in the depth of patterns, whereas `is_useful` is worst-case
981+
/// exponential (exhaustiveness is NP-complete).
982+
fn lint_overlapping_range_endpoints<'p, 'tcx>(
983+
cx: &MatchCheckCtxt<'p, 'tcx>,
984+
column: &[&DeconstructedPat<'p, 'tcx>],
985+
lint_root: HirId,
986+
) {
987+
if column.is_empty() {
988+
return;
989+
}
990+
let ty = column[0].ty();
991+
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
992+
993+
let column_ctors = column.iter().map(|p| p.ctor());
994+
let set = ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, column_ctors);
995+
996+
if IntRange::is_integral(ty) {
997+
// If two ranges overlapped, the split set will contain their intersection as a singleton.
998+
let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range());
999+
for overlap_range in split_int_ranges.clone() {
1000+
if overlap_range.is_singleton() {
1001+
let overlap: u128 = overlap_range.boundaries().0;
1002+
// Spans of ranges that start or end with the overlap.
1003+
let mut prefixes: SmallVec<[_; 1]> = Default::default();
1004+
let mut suffixes: SmallVec<[_; 1]> = Default::default();
1005+
// Iterate on patterns that contained `overlap`.
1006+
for pat in column {
1007+
let this_span = pat.span();
1008+
let Constructor::IntRange(this_range) = pat.ctor() else { continue };
1009+
if this_range.is_singleton() {
1010+
// Don't lint when one of the ranges is a singleton.
1011+
continue;
1012+
}
1013+
let mut this_overlaps: SmallVec<[_; 1]> = Default::default();
1014+
let (start, end) = this_range.boundaries();
1015+
if start == overlap {
1016+
if !prefixes.is_empty() {
1017+
this_overlaps = prefixes.clone();
1018+
}
1019+
suffixes.push(this_span)
1020+
} else if end == overlap {
1021+
if !suffixes.is_empty() {
1022+
this_overlaps = suffixes.clone();
1023+
}
1024+
prefixes.push(this_span)
1025+
}
1026+
if !this_overlaps.is_empty() {
1027+
let overlap_as_pat = overlap_range.to_pat(pcx.cx.tcx, pcx.ty);
1028+
let overlaps: Vec<_> = this_overlaps
1029+
.into_iter()
1030+
.map(|span| Overlap { range: overlap_as_pat.clone(), span })
1031+
.collect();
1032+
pcx.cx.tcx.emit_spanned_lint(
1033+
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
1034+
lint_root,
1035+
this_span,
1036+
OverlappingRangeEndpoints { overlap: overlaps, range: this_span },
1037+
);
1038+
}
1039+
}
1040+
}
1041+
}
1042+
}
1043+
1044+
// Recurse into the fields.
1045+
for ctor in set.present {
1046+
let arity = ctor.arity(pcx);
1047+
if arity == 0 {
1048+
continue;
1049+
}
1050+
1051+
// We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These
1052+
// columns may have different lengths in the presence of or-patterns (this is why we can't
1053+
// reuse `Matrix`).
1054+
let mut specialized_columns: Vec<Vec<_>> = (0..arity).map(|_| Vec::new()).collect();
1055+
let relevant_patterns = column.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor()));
1056+
for pat in relevant_patterns {
1057+
let specialized = pat.specialize(pcx, &ctor);
1058+
for (subpat, sub_column) in specialized.iter().zip(&mut specialized_columns) {
1059+
if subpat.is_or_pat() {
1060+
sub_column.extend(subpat.iter_fields())
1061+
} else {
1062+
sub_column.push(subpat)
1063+
}
1064+
}
1065+
}
1066+
1067+
for col in specialized_columns.iter() {
1068+
lint_overlapping_range_endpoints(cx, col.as_slice(), lint_root);
1069+
}
1070+
}
1071+
}
1072+
9891073
/// The arm of a match expression.
9901074
#[derive(Clone, Copy, Debug)]
9911075
pub(crate) struct MatchArm<'p, 'tcx> {
@@ -1056,6 +1140,9 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
10561140
NoWitnesses { .. } => bug!(),
10571141
};
10581142

1143+
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
1144+
lint_overlapping_range_endpoints(cx, &pat_column, lint_root);
1145+
10591146
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
10601147
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
10611148
if cx.refutable
@@ -1065,9 +1152,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
10651152
rustc_session::lint::Level::Allow
10661153
)
10671154
{
1068-
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
10691155
let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column);
1070-
10711156
if !witnesses.is_empty() {
10721157
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
10731158
// is not exhaustive enough.

tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ macro_rules! m {
88
$t2 => {}
99
_ => {}
1010
}
11-
}
11+
};
1212
}
1313

1414
fn main() {
1515
m!(0u8, 20..=30, 30..=40); //~ ERROR multiple patterns overlap on their endpoints
1616
m!(0u8, 30..=40, 20..=30); //~ ERROR multiple patterns overlap on their endpoints
1717
m!(0u8, 20..=30, 31..=40);
1818
m!(0u8, 20..=30, 29..=40);
19-
m!(0u8, 20.. 30, 29..=40); //~ ERROR multiple patterns overlap on their endpoints
20-
m!(0u8, 20.. 30, 28..=40);
21-
m!(0u8, 20.. 30, 30..=40);
19+
m!(0u8, 20..30, 29..=40); //~ ERROR multiple patterns overlap on their endpoints
20+
m!(0u8, 20..30, 28..=40);
21+
m!(0u8, 20..30, 30..=40);
2222
m!(0u8, 20..=30, 30..=30);
2323
m!(0u8, 20..=30, 30..=31); //~ ERROR multiple patterns overlap on their endpoints
2424
m!(0u8, 20..=30, 29..=30);
@@ -28,27 +28,29 @@ fn main() {
2828
m!(0u8, 20..=30, 20);
2929
m!(0u8, 20..=30, 25);
3030
m!(0u8, 20..=30, 30);
31-
m!(0u8, 20.. 30, 29);
31+
m!(0u8, 20..30, 29);
3232
m!(0u8, 20, 20..=30);
3333
m!(0u8, 25, 20..=30);
3434
m!(0u8, 30, 20..=30);
3535

3636
match 0u8 {
3737
0..=10 => {}
3838
20..=30 => {}
39-
10..=20 => {} //~ ERROR multiple patterns overlap on their endpoints
39+
10..=20 => {}
40+
//~^ ERROR multiple patterns overlap on their endpoints
41+
//~| ERROR multiple patterns overlap on their endpoints
4042
_ => {}
4143
}
4244
match (0u8, true) {
4345
(0..=10, true) => {}
44-
(10..20, true) => {} // not detected
45-
(10..20, false) => {}
46+
(10..20, true) => {} //~ ERROR multiple patterns overlap on their endpoints
47+
(10..20, false) => {} //~ ERROR multiple patterns overlap on their endpoints
4648
_ => {}
4749
}
4850
match (true, 0u8) {
4951
(true, 0..=10) => {}
5052
(true, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints
51-
(false, 10..20) => {}
53+
(false, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints
5254
_ => {}
5355
}
5456
match Some(0u8) {

0 commit comments

Comments
 (0)