Skip to content

Commit d5070e3

Browse files
committed
Lint overlapping ranges as a separate pass
1 parent beecd93 commit d5070e3

File tree

5 files changed

+150
-104
lines changed

5 files changed

+150
-104
lines changed

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

+6-70
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

+85-19
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,9 @@
308308
use self::ArmType::*;
309309
use self::Usefulness::*;
310310
use super::deconstruct_pat::{
311-
Constructor, ConstructorSet, DeconstructedPat, SplitConstructorSet, WitnessPat,
311+
Constructor, ConstructorSet, DeconstructedPat, IntRange, SplitConstructorSet, WitnessPat,
312312
};
313-
use crate::errors::{NonExhaustiveOmittedPattern, Uncovered};
313+
use crate::errors::{NonExhaustiveOmittedPattern, Overlap, OverlappingRangeEndpoints, Uncovered};
314314

315315
use rustc_data_structures::captures::Captures;
316316

@@ -319,6 +319,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
319319
use rustc_hir::def_id::DefId;
320320
use rustc_hir::HirId;
321321
use rustc_middle::ty::{self, Ty, TyCtxt};
322+
use rustc_session::lint;
322323
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
323324
use rustc_span::{Span, DUMMY_SP};
324325

@@ -475,11 +476,6 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> {
475476
Matrix { patterns: vec![] }
476477
}
477478

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

836832
let v_ctor = v.head().ctor();
837833
debug!(?v_ctor);
838-
if let Constructor::IntRange(ctor_range) = &v_ctor {
839-
// Lint on likely incorrect range patterns (#63987)
840-
ctor_range.lint_overlapping_range_endpoints(
841-
pcx,
842-
matrix.heads(),
843-
matrix.column_count().unwrap_or(0),
844-
lint_root,
845-
)
846-
}
847834
// We split the head constructor of `v`.
848835
let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
849836
// For each constructor, we compute whether there's a value that starts with it that would
@@ -903,6 +890,9 @@ impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
903890
let column_ctors = self.patterns.iter().map(|p| p.ctor());
904891
ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, column_ctors)
905892
}
893+
fn iter<'a>(&'a self) -> impl Iterator<Item = &'p DeconstructedPat<'p, 'tcx>> + Captures<'a> {
894+
self.patterns.iter().copied()
895+
}
906896

907897
/// Does specialization: given a constructor, this takes the patterns from the column that match
908898
/// the constructor, and outputs their fields.
@@ -992,6 +982,81 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>(
992982
witnesses
993983
}
994984

985+
/// Traverse the patterns to warn the user about ranges that overlap on their endpoints.
986+
#[instrument(level = "debug", skip(cx, lint_root))]
987+
fn lint_overlapping_range_endpoints<'p, 'tcx>(
988+
cx: &MatchCheckCtxt<'p, 'tcx>,
989+
column: &PatternColumn<'p, 'tcx>,
990+
lint_root: HirId,
991+
) {
992+
let Some(ty) = column.head_ty() else {
993+
return;
994+
};
995+
let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level: false };
996+
997+
let set = column.analyze_ctors(pcx);
998+
999+
if IntRange::is_integral(ty) {
1000+
let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| {
1001+
let overlap_as_pat = overlap.to_pat(cx.tcx, ty);
1002+
let overlaps: Vec<_> = overlapped_spans
1003+
.iter()
1004+
.copied()
1005+
.map(|span| Overlap { range: overlap_as_pat.clone(), span })
1006+
.collect();
1007+
cx.tcx.emit_spanned_lint(
1008+
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
1009+
lint_root,
1010+
this_span,
1011+
OverlappingRangeEndpoints { overlap: overlaps, range: this_span },
1012+
);
1013+
};
1014+
1015+
// If two ranges overlapped, the split set will contain their intersection as a singleton.
1016+
let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range());
1017+
for overlap_range in split_int_ranges.clone() {
1018+
if overlap_range.is_singleton() {
1019+
let overlap: u128 = overlap_range.boundaries().0;
1020+
// Spans of ranges that start or end with the overlap.
1021+
let mut prefixes: SmallVec<[_; 1]> = Default::default();
1022+
let mut suffixes: SmallVec<[_; 1]> = Default::default();
1023+
// Iterate on patterns that contained `overlap`.
1024+
for pat in column.iter() {
1025+
let this_span = pat.span();
1026+
let Constructor::IntRange(this_range) = pat.ctor() else { continue };
1027+
if this_range.is_singleton() {
1028+
// Don't lint when one of the ranges is a singleton.
1029+
continue;
1030+
}
1031+
let (start, end) = this_range.boundaries();
1032+
if start == overlap {
1033+
// `this_range` looks like `overlap..=end`; it overlaps with any ranges that
1034+
// look like `start..=overlap`.
1035+
if !prefixes.is_empty() {
1036+
emit_lint(overlap_range, this_span, &prefixes);
1037+
}
1038+
suffixes.push(this_span)
1039+
} else if end == overlap {
1040+
// `this_range` looks like `start..=overlap`; it overlaps with any ranges
1041+
// that look like `overlap..=end`.
1042+
if !suffixes.is_empty() {
1043+
emit_lint(overlap_range, this_span, &suffixes);
1044+
}
1045+
prefixes.push(this_span)
1046+
}
1047+
}
1048+
}
1049+
}
1050+
} else {
1051+
// Recurse into the fields.
1052+
for ctor in set.present {
1053+
for col in column.specialize(pcx, &ctor) {
1054+
lint_overlapping_range_endpoints(cx, &col, lint_root);
1055+
}
1056+
}
1057+
}
1058+
}
1059+
9951060
/// The arm of a match expression.
9961061
#[derive(Clone, Copy, Debug)]
9971062
pub(crate) struct MatchArm<'p, 'tcx> {
@@ -1062,6 +1127,10 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
10621127
NoWitnesses { .. } => bug!(),
10631128
};
10641129

1130+
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
1131+
let pat_column = PatternColumn::new(pat_column);
1132+
lint_overlapping_range_endpoints(cx, &pat_column, lint_root);
1133+
10651134
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
10661135
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
10671136
if cx.refutable
@@ -1071,10 +1140,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
10711140
rustc_session::lint::Level::Allow
10721141
)
10731142
{
1074-
let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::<Vec<_>>();
1075-
let pat_column = PatternColumn::new(pat_column);
10761143
let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column);
1077-
10781144
if !witnesses.is_empty() {
10791145
// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns`
10801146
// is not exhaustive enough.

tests/ui/mir/mir_match_test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![feature(exclusive_range_pattern)]
2+
#![allow(overlapping_range_endpoints)]
23

34
// run-pass
45

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

+11-9
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)