Skip to content

Commit 3330ee9

Browse files
committed
Only lint ranges that really overlap
1 parent 3653beb commit 3330ee9

File tree

5 files changed

+83
-111
lines changed

5 files changed

+83
-111
lines changed

compiler/rustc_pattern_analysis/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ pub fn analyze_match<'p, 'tcx>(
122122
let pat_column = PatternColumn::new(arms);
123123

124124
// Lint ranges that overlap on their endpoints, which is likely a mistake.
125-
lint_overlapping_range_endpoints(cx, &pat_column);
125+
if !report.overlapping_range_endpoints.is_empty() {
126+
lint_overlapping_range_endpoints(cx, &report.overlapping_range_endpoints);
127+
}
126128

127129
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
128130
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.

compiler/rustc_pattern_analysis/src/lints.rs

+2-82
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
use smallvec::SmallVec;
2-
3-
use rustc_data_structures::captures::Captures;
4-
use rustc_middle::ty::{self, Ty};
1+
use rustc_middle::ty::Ty;
52
use rustc_session::lint;
63
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
74

8-
use crate::constructor::MaybeInfiniteInt;
95
use crate::errors::{
106
NonExhaustiveOmittedPattern, NonExhaustiveOmittedPatternLintOnArm, Overlap,
117
OverlappingRangeEndpoints, Uncovered,
@@ -14,7 +10,6 @@ use crate::rustc::{
1410
self, Constructor, DeconstructedPat, MatchArm, MatchCtxt, PlaceCtxt, RustcMatchCheckCtxt,
1511
SplitConstructorSet, WitnessPat,
1612
};
17-
use crate::usefulness::OverlappingRanges;
1813
use crate::TypeCx;
1914

2015
/// A column of patterns in the matrix, where a column is the intuitive notion of "subpatterns that
@@ -64,10 +59,6 @@ impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
6459
pcx.ctors_for_ty().split(pcx, column_ctors)
6560
}
6661

67-
fn iter(&self) -> impl Iterator<Item = &'p DeconstructedPat<'p, 'tcx>> + Captures<'_> {
68-
self.patterns.iter().copied()
69-
}
70-
7162
/// Does specialization: given a constructor, this takes the patterns from the column that match
7263
/// the constructor, and outputs their fields.
7364
/// This returns one column per field of the constructor. They usually all have the same length
@@ -212,81 +203,10 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
212203
}
213204
}
214205

215-
/// Traverse the patterns to warn the user about ranges that overlap on their endpoints.
216-
#[instrument(level = "debug", skip(cx))]
217-
pub(crate) fn collect_overlapping_range_endpoints<'a, 'p, 'tcx>(
218-
cx: MatchCtxt<'a, 'p, 'tcx>,
219-
column: &PatternColumn<'p, 'tcx>,
220-
overlapping_range_endpoints: &mut Vec<rustc::OverlappingRanges<'p, 'tcx>>,
221-
) {
222-
let Some(ty) = column.head_ty(cx) else {
223-
return;
224-
};
225-
let pcx = &PlaceCtxt::new_dummy(cx, ty);
226-
227-
let set = column.analyze_ctors(pcx);
228-
229-
if matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_)) {
230-
// If two ranges overlapped, the split set will contain their intersection as a singleton.
231-
let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range());
232-
for overlap_range in split_int_ranges.clone() {
233-
if overlap_range.is_singleton() {
234-
let overlap: MaybeInfiniteInt = overlap_range.lo;
235-
// Ranges that look like `lo..=overlap`.
236-
let mut prefixes: SmallVec<[_; 1]> = Default::default();
237-
// Ranges that look like `overlap..=hi`.
238-
let mut suffixes: SmallVec<[_; 1]> = Default::default();
239-
// Iterate on patterns that contained `overlap`.
240-
for pat in column.iter() {
241-
let Constructor::IntRange(this_range) = pat.ctor() else { continue };
242-
if this_range.is_singleton() {
243-
// Don't lint when one of the ranges is a singleton.
244-
continue;
245-
}
246-
if this_range.lo == overlap {
247-
// `this_range` looks like `overlap..=this_range.hi`; it overlaps with any
248-
// ranges that look like `lo..=overlap`.
249-
if !prefixes.is_empty() {
250-
overlapping_range_endpoints.push(OverlappingRanges {
251-
pat,
252-
overlaps_on: *overlap_range,
253-
overlaps_with: prefixes.as_slice().to_vec(),
254-
});
255-
}
256-
suffixes.push(pat)
257-
} else if this_range.hi == overlap.plus_one() {
258-
// `this_range` looks like `this_range.lo..=overlap`; it overlaps with any
259-
// ranges that look like `overlap..=hi`.
260-
if !suffixes.is_empty() {
261-
overlapping_range_endpoints.push(OverlappingRanges {
262-
pat,
263-
overlaps_on: *overlap_range,
264-
overlaps_with: suffixes.as_slice().to_vec(),
265-
});
266-
}
267-
prefixes.push(pat)
268-
}
269-
}
270-
}
271-
}
272-
} else {
273-
// Recurse into the fields.
274-
for ctor in set.present {
275-
for col in column.specialize(pcx, &ctor) {
276-
collect_overlapping_range_endpoints(cx, &col, overlapping_range_endpoints);
277-
}
278-
}
279-
}
280-
}
281-
282-
#[instrument(level = "debug", skip(cx))]
283206
pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>(
284207
cx: MatchCtxt<'a, 'p, 'tcx>,
285-
column: &PatternColumn<'p, 'tcx>,
208+
overlapping_range_endpoints: &[rustc::OverlappingRanges<'p, 'tcx>],
286209
) {
287-
let mut overlapping_range_endpoints = Vec::new();
288-
collect_overlapping_range_endpoints(cx, column, &mut overlapping_range_endpoints);
289-
290210
let rcx = cx.tycx;
291211
for overlap in overlapping_range_endpoints {
292212
let overlap_as_pat = rcx.hoist_pat_range(&overlap.overlaps_on, overlap.pat.ty());

compiler/rustc_pattern_analysis/src/usefulness.rs

+75-3
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,7 @@ impl<Cx: TypeCx> WitnessMatrix<Cx> {
13441344
fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
13451345
mcx: MatchCtxt<'a, 'p, Cx>,
13461346
matrix: &mut Matrix<'p, Cx>,
1347+
overlapping_range_endpoints: &mut Vec<OverlappingRanges<'p, Cx>>,
13471348
is_top_level: bool,
13481349
) -> WitnessMatrix<Cx> {
13491350
debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count()));
@@ -1423,7 +1424,12 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
14231424
let ctor_is_relevant = matches!(ctor, Constructor::Missing) || missing_ctors.is_empty();
14241425
let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor, ctor_is_relevant);
14251426
let mut witnesses = ensure_sufficient_stack(|| {
1426-
compute_exhaustiveness_and_usefulness(mcx, &mut spec_matrix, false)
1427+
compute_exhaustiveness_and_usefulness(
1428+
mcx,
1429+
&mut spec_matrix,
1430+
overlapping_range_endpoints,
1431+
false,
1432+
)
14271433
});
14281434

14291435
// Transform witnesses for `spec_matrix` into witnesses for `matrix`.
@@ -1447,6 +1453,65 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
14471453
}
14481454
}
14491455
}
1456+
1457+
// Detect overlapping ranges.
1458+
if let Constructor::IntRange(overlap_range) = ctor {
1459+
// If two ranges overlap on their endpoing, that endpoint will show up as a singleton in
1460+
// the splitted set.
1461+
if overlap_range.is_singleton() {
1462+
let overlap = overlap_range.lo;
1463+
// Ranges that look like `lo..=overlap`.
1464+
let mut prefixes: SmallVec<[_; 1]> = Default::default();
1465+
// Ranges that look like `overlap..=hi`.
1466+
let mut suffixes: SmallVec<[_; 1]> = Default::default();
1467+
// Iterate on patterns that contained `overlap`.
1468+
for (row_id, row) in matrix.rows().enumerate() {
1469+
let pat = row.head();
1470+
let Constructor::IntRange(this_range) = pat.ctor() else { continue };
1471+
// Don't lint when one of the ranges is a singleton.
1472+
if this_range.is_singleton() {
1473+
continue;
1474+
}
1475+
if this_range.lo == overlap {
1476+
// `this_range` looks like `overlap..=this_range.hi`; it overlaps with any
1477+
// ranges that look like `lo..=overlap`.
1478+
if !prefixes.is_empty() {
1479+
let overlaps_with: Vec<_> = prefixes
1480+
.iter()
1481+
.filter(|&&other_row_id| row.intersects.contains(other_row_id))
1482+
.map(|&other_row_id| matrix.rows[other_row_id].head())
1483+
.collect();
1484+
if !overlaps_with.is_empty() {
1485+
overlapping_range_endpoints.push(OverlappingRanges {
1486+
pat,
1487+
overlaps_on: overlap_range,
1488+
overlaps_with,
1489+
});
1490+
}
1491+
}
1492+
suffixes.push(row_id)
1493+
} else if this_range.hi == overlap.plus_one() {
1494+
// `this_range` looks like `this_range.lo..=overlap`; it overlaps with any
1495+
// ranges that look like `overlap..=hi`.
1496+
if !suffixes.is_empty() {
1497+
let overlaps_with: Vec<_> = suffixes
1498+
.iter()
1499+
.filter(|&&other_row_id| row.intersects.contains(other_row_id))
1500+
.map(|&other_row_id| matrix.rows[other_row_id].head())
1501+
.collect();
1502+
if !overlaps_with.is_empty() {
1503+
overlapping_range_endpoints.push(OverlappingRanges {
1504+
pat,
1505+
overlaps_on: overlap_range,
1506+
overlaps_with,
1507+
});
1508+
}
1509+
}
1510+
prefixes.push(row_id)
1511+
}
1512+
}
1513+
}
1514+
}
14501515
}
14511516

14521517
// Record usefulness in the patterns.
@@ -1486,6 +1551,7 @@ pub struct UsefulnessReport<'p, Cx: TypeCx> {
14861551
/// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of
14871552
/// exhaustiveness.
14881553
pub non_exhaustiveness_witnesses: Vec<WitnessPat<Cx>>,
1554+
pub overlapping_range_endpoints: Vec<OverlappingRanges<'p, Cx>>,
14891555
}
14901556

14911557
/// Computes whether a match is exhaustive and which of its arms are useful.
@@ -1496,8 +1562,14 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
14961562
scrut_ty: Cx::Ty,
14971563
scrut_validity: ValidityConstraint,
14981564
) -> UsefulnessReport<'p, Cx> {
1565+
let mut overlapping_range_endpoints = Vec::new();
14991566
let mut matrix = Matrix::new(cx.wildcard_arena, arms, scrut_ty, scrut_validity);
1500-
let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(cx, &mut matrix, true);
1567+
let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(
1568+
cx,
1569+
&mut matrix,
1570+
&mut overlapping_range_endpoints,
1571+
true,
1572+
);
15011573

15021574
let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column();
15031575
let arm_usefulness: Vec<_> = arms
@@ -1514,5 +1586,5 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
15141586
(arm, usefulness)
15151587
})
15161588
.collect();
1517-
UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses }
1589+
UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses, overlapping_range_endpoints }
15181590
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ fn main() {
4444
match (0u8, true) {
4545
(0..=10, true) => {}
4646
(10..20, true) => {} //~ ERROR multiple patterns overlap on their endpoints
47-
(10..20, false) => {} //~ ERROR multiple patterns overlap on their endpoints
47+
(10..20, false) => {}
4848
_ => {}
4949
}
5050
match (true, 0u8) {
5151
(true, 0..=10) => {}
5252
(true, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints
53-
(false, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints
53+
(false, 10..20) => {}
5454
_ => {}
5555
}
5656
match Some(0u8) {

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

+1-23
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,6 @@ LL | (10..20, true) => {}
8484
|
8585
= note: you likely meant to write mutually exclusive ranges
8686

87-
error: multiple patterns overlap on their endpoints
88-
--> $DIR/overlapping_range_endpoints.rs:47:10
89-
|
90-
LL | (0..=10, true) => {}
91-
| ------ this range overlaps on `10_u8`...
92-
LL | (10..20, true) => {}
93-
LL | (10..20, false) => {}
94-
| ^^^^^^ ... with this range
95-
|
96-
= note: you likely meant to write mutually exclusive ranges
97-
9887
error: multiple patterns overlap on their endpoints
9988
--> $DIR/overlapping_range_endpoints.rs:52:16
10089
|
@@ -105,17 +94,6 @@ LL | (true, 10..20) => {}
10594
|
10695
= note: you likely meant to write mutually exclusive ranges
10796

108-
error: multiple patterns overlap on their endpoints
109-
--> $DIR/overlapping_range_endpoints.rs:53:17
110-
|
111-
LL | (true, 0..=10) => {}
112-
| ------ this range overlaps on `10_u8`...
113-
LL | (true, 10..20) => {}
114-
LL | (false, 10..20) => {}
115-
| ^^^^^^ ... with this range
116-
|
117-
= note: you likely meant to write mutually exclusive ranges
118-
11997
error: multiple patterns overlap on their endpoints
12098
--> $DIR/overlapping_range_endpoints.rs:58:14
12199
|
@@ -126,5 +104,5 @@ LL | Some(10..20) => {}
126104
|
127105
= note: you likely meant to write mutually exclusive ranges
128106

129-
error: aborting due to 12 previous errors
107+
error: aborting due to 10 previous errors
130108

0 commit comments

Comments
 (0)