Skip to content

Commit a24f4db

Browse files
committed
Only lint ranges that really overlap
1 parent 6f6ba25 commit a24f4db

File tree

6 files changed

+137
-115
lines changed

6 files changed

+137
-115
lines changed

compiler/rustc_pattern_analysis/src/lib.rs

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

127127
// Lint ranges that overlap on their endpoints, which is likely a mistake.
128-
lint_overlapping_range_endpoints(cx, &pat_column)?;
128+
if !report.overlapping_range_endpoints.is_empty() {
129+
lint_overlapping_range_endpoints(cx, &report.overlapping_range_endpoints);
130+
}
129131

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

compiler/rustc_pattern_analysis/src/lints.rs

+2-85
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
use smallvec::SmallVec;
2-
3-
use rustc_data_structures::captures::Captures;
4-
use rustc_middle::ty;
51
use rustc_session::lint;
62
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
73
use rustc_span::ErrorGuaranteed;
84

9-
use crate::constructor::MaybeInfiniteInt;
105
use crate::errors::{
116
self, NonExhaustiveOmittedPattern, NonExhaustiveOmittedPatternLintOnArm, Uncovered,
127
};
@@ -15,7 +10,6 @@ use crate::rustc::{
1510
self, Constructor, DeconstructedPat, MatchArm, MatchCtxt, PlaceCtxt, RevealedTy,
1611
RustcMatchCheckCtxt, SplitConstructorSet, WitnessPat,
1712
};
18-
use crate::usefulness::OverlappingRanges;
1913

2014
/// A column of patterns in the matrix, where a column is the intuitive notion of "subpatterns that
2115
/// inspect the same subvalue/place".
@@ -68,10 +62,6 @@ impl<'p, 'tcx> PatternColumn<'p, 'tcx> {
6862
Ok(ctors_for_ty.split(pcx, column_ctors))
6963
}
7064

71-
fn iter(&self) -> impl Iterator<Item = &'p DeconstructedPat<'p, 'tcx>> + Captures<'_> {
72-
self.patterns.iter().copied()
73-
}
74-
7565
/// Does specialization: given a constructor, this takes the patterns from the column that match
7666
/// the constructor, and outputs their fields.
7767
/// This returns one column per field of the constructor. They usually all have the same length
@@ -207,82 +197,10 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
207197
Ok(())
208198
}
209199

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

compiler/rustc_pattern_analysis/src/usefulness.rs

+113-4
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,83 @@ impl<Cx: TypeCx> WitnessMatrix<Cx> {
13281328
}
13291329
}
13301330

1331+
/// Collect ranges that overlap like `lo..=overlap`/`overlap..=hi`. Must be called during
1332+
/// exhaustiveness checking, if we find a singleton range after constructor splitting. This reuses
1333+
/// row intersection information to only detect ranges that truly overlap.
1334+
///
1335+
/// If two ranges overlapped, the split set will contain their intersection as a singleton.
1336+
/// Specialization will then select rows that match the overlap, and exhaustiveness will compute
1337+
/// which rows have an intersection that includes the overlap. That gives us all the info we need to
1338+
/// compute overlapping ranges without false positives.
1339+
///
1340+
/// We can however get false negatives because exhaustiveness does not explore all cases. See the
1341+
/// section on relevancy at the top of the file.
1342+
fn collect_overlapping_range_endpoints<'p, Cx: TypeCx>(
1343+
overlap_range: IntRange,
1344+
matrix: &Matrix<'p, Cx>,
1345+
specialized_matrix: &Matrix<'p, Cx>,
1346+
overlapping_range_endpoints: &mut Vec<OverlappingRanges<'p, Cx>>,
1347+
) {
1348+
let overlap = overlap_range.lo;
1349+
// Ranges that look like `lo..=overlap`.
1350+
let mut prefixes: SmallVec<[_; 1]> = Default::default();
1351+
// Ranges that look like `overlap..=hi`.
1352+
let mut suffixes: SmallVec<[_; 1]> = Default::default();
1353+
// Iterate on patterns that contained `overlap`. We iterate on `specialized_matrix` which
1354+
// contains only rows that matched the current `ctor` as well as accurate intersection
1355+
// information. It doesn't contain the column that contains the range; that can be found in
1356+
// `matrix`.
1357+
for (child_row_id, child_row) in specialized_matrix.rows().enumerate() {
1358+
let PatOrWild::Pat(pat) = matrix.rows[child_row.parent_row].head() else { continue };
1359+
let Constructor::IntRange(this_range) = pat.ctor() else { continue };
1360+
// Don't lint when one of the ranges is a singleton.
1361+
if this_range.is_singleton() {
1362+
continue;
1363+
}
1364+
if this_range.lo == overlap {
1365+
// `this_range` looks like `overlap..=this_range.hi`; it overlaps with any
1366+
// ranges that look like `lo..=overlap`.
1367+
if !prefixes.is_empty() {
1368+
let overlaps_with: Vec<_> = prefixes
1369+
.iter()
1370+
.filter(|&&(other_child_row_id, _)| {
1371+
child_row.intersects.contains(other_child_row_id)
1372+
})
1373+
.map(|&(_, pat)| pat)
1374+
.collect();
1375+
if !overlaps_with.is_empty() {
1376+
overlapping_range_endpoints.push(OverlappingRanges {
1377+
pat,
1378+
overlaps_on: overlap_range,
1379+
overlaps_with,
1380+
});
1381+
}
1382+
}
1383+
suffixes.push((child_row_id, pat))
1384+
} else if this_range.hi == overlap.plus_one() {
1385+
// `this_range` looks like `this_range.lo..=overlap`; it overlaps with any
1386+
// ranges that look like `overlap..=hi`.
1387+
if !suffixes.is_empty() {
1388+
let overlaps_with: Vec<_> = suffixes
1389+
.iter()
1390+
.filter(|&&(other_child_row_id, _)| {
1391+
child_row.intersects.contains(other_child_row_id)
1392+
})
1393+
.map(|&(_, pat)| pat)
1394+
.collect();
1395+
if !overlaps_with.is_empty() {
1396+
overlapping_range_endpoints.push(OverlappingRanges {
1397+
pat,
1398+
overlaps_on: overlap_range,
1399+
overlaps_with,
1400+
});
1401+
}
1402+
}
1403+
prefixes.push((child_row_id, pat))
1404+
}
1405+
}
1406+
}
1407+
13311408
/// The core of the algorithm.
13321409
///
13331410
/// This recursively computes witnesses of the non-exhaustiveness of `matrix` (if any). Also tracks
@@ -1346,6 +1423,7 @@ impl<Cx: TypeCx> WitnessMatrix<Cx> {
13461423
fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
13471424
mcx: MatchCtxt<'a, 'p, Cx>,
13481425
matrix: &mut Matrix<'p, Cx>,
1426+
overlapping_range_endpoints: &mut Vec<OverlappingRanges<'p, Cx>>,
13491427
is_top_level: bool,
13501428
) -> Result<WitnessMatrix<Cx>, Cx::Error> {
13511429
debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count()));
@@ -1425,7 +1503,12 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
14251503
let ctor_is_relevant = matches!(ctor, Constructor::Missing) || missing_ctors.is_empty();
14261504
let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor, ctor_is_relevant);
14271505
let mut witnesses = ensure_sufficient_stack(|| {
1428-
compute_exhaustiveness_and_usefulness(mcx, &mut spec_matrix, false)
1506+
compute_exhaustiveness_and_usefulness(
1507+
mcx,
1508+
&mut spec_matrix,
1509+
overlapping_range_endpoints,
1510+
false,
1511+
)
14291512
})?;
14301513

14311514
// Transform witnesses for `spec_matrix` into witnesses for `matrix`.
@@ -1447,6 +1530,21 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
14471530
}
14481531
}
14491532
}
1533+
1534+
// Detect ranges that overlap on their endpoints.
1535+
if let Constructor::IntRange(overlap_range) = ctor {
1536+
if overlap_range.is_singleton()
1537+
&& spec_matrix.rows.len() >= 2
1538+
&& spec_matrix.rows.iter().any(|row| !row.intersects.is_empty())
1539+
{
1540+
collect_overlapping_range_endpoints(
1541+
overlap_range,
1542+
matrix,
1543+
&spec_matrix,
1544+
overlapping_range_endpoints,
1545+
);
1546+
}
1547+
}
14501548
}
14511549

14521550
// Record usefulness in the patterns.
@@ -1487,6 +1585,7 @@ pub struct UsefulnessReport<'p, Cx: TypeCx> {
14871585
/// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of
14881586
/// exhaustiveness.
14891587
pub non_exhaustiveness_witnesses: Vec<WitnessPat<Cx>>,
1588+
pub overlapping_range_endpoints: Vec<OverlappingRanges<'p, Cx>>,
14901589
}
14911590

14921591
/// Computes whether a match is exhaustive and which of its arms are useful.
@@ -1497,9 +1596,14 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
14971596
scrut_ty: Cx::Ty,
14981597
scrut_validity: ValidityConstraint,
14991598
) -> Result<UsefulnessReport<'p, Cx>, Cx::Error> {
1599+
let mut overlapping_range_endpoints = Vec::new();
15001600
let mut matrix = Matrix::new(arms, scrut_ty, scrut_validity);
1501-
let non_exhaustiveness_witnesses =
1502-
compute_exhaustiveness_and_usefulness(cx, &mut matrix, true)?;
1601+
let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(
1602+
cx,
1603+
&mut matrix,
1604+
&mut overlapping_range_endpoints,
1605+
true,
1606+
)?;
15031607

15041608
let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column();
15051609
let arm_usefulness: Vec<_> = arms
@@ -1516,5 +1620,10 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
15161620
(arm, usefulness)
15171621
})
15181622
.collect();
1519-
Ok(UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses })
1623+
1624+
Ok(UsefulnessReport {
1625+
arm_usefulness,
1626+
non_exhaustiveness_witnesses,
1627+
overlapping_range_endpoints,
1628+
})
15201629
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// check-pass
2+
fn main() {
3+
match (0i8, 0i8) {
4+
(0, _) => {}
5+
(..=-1, ..=0) => {}
6+
(1.., 0..) => {}
7+
(1.., ..=-1) | (..=-1, 1..) => {}
8+
}
9+
}

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,25 @@ 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) {
5757
Some(0..=10) => {}
5858
Some(10..20) => {} //~ ERROR multiple patterns overlap on their endpoints
5959
_ => {}
6060
}
61+
62+
// The lint has false negatives when we skip some cases because of relevancy.
63+
match (true, true, 0u8) {
64+
(true, _, 0..=10) => {}
65+
(_, true, 10..20) => {}
66+
_ => {}
67+
}
6168
}

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)