Skip to content

Commit cb98877

Browse files
committed
Auto merge of #119396 - Nadrieril:intersection-tracking, r=<try>
Experiment: track overlapping ranges precisely The `overlapping_range_endpoints` lint has false positives, e.g. #117648. I expected that removing these false positives would have too much of a perf impact but never measured it. This PR is an experiment to see if the perf loss is manageable. r? `@ghost`
2 parents 3ee6710 + 3330ee9 commit cb98877

File tree

6 files changed

+149
-129
lines changed

6 files changed

+149
-129
lines changed

compiler/rustc_pattern_analysis/src/lib.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,10 @@ pub fn analyze_match<'p, 'tcx>(
121121

122122
let pat_column = PatternColumn::new(arms);
123123

124-
// Lint on ranges that overlap on their endpoints, which is likely a mistake.
125-
lint_overlapping_range_endpoints(cx, &pat_column);
124+
// Lint ranges that overlap on their endpoints, which is likely a mistake.
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

+19-80
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
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;
7-
use rustc_span::Span;
84

9-
use crate::constructor::{IntRange, MaybeInfiniteInt};
105
use crate::errors::{
116
NonExhaustiveOmittedPattern, NonExhaustiveOmittedPatternLintOnArm, Overlap,
127
OverlappingRangeEndpoints, Uncovered,
138
};
149
use crate::rustc::{
15-
Constructor, DeconstructedPat, MatchArm, MatchCtxt, PlaceCtxt, RustcMatchCheckCtxt,
10+
self, Constructor, DeconstructedPat, MatchArm, MatchCtxt, PlaceCtxt, RustcMatchCheckCtxt,
1611
SplitConstructorSet, WitnessPat,
1712
};
1813
use crate::TypeCx;
@@ -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,77 +203,25 @@ 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))]
217206
pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>(
218207
cx: MatchCtxt<'a, 'p, 'tcx>,
219-
column: &PatternColumn<'p, 'tcx>,
208+
overlapping_range_endpoints: &[rustc::OverlappingRanges<'p, 'tcx>],
220209
) {
221-
let Some(ty) = column.head_ty(cx) else {
222-
return;
223-
};
224-
let pcx = &PlaceCtxt::new_dummy(cx, ty);
225-
let rcx: &RustcMatchCheckCtxt<'_, '_> = cx.tycx;
226-
227-
let set = column.analyze_ctors(pcx);
228-
229-
if matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_)) {
230-
let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| {
231-
let overlap_as_pat = rcx.hoist_pat_range(overlap, ty);
232-
let overlaps: Vec<_> = overlapped_spans
233-
.iter()
234-
.copied()
235-
.map(|span| Overlap { range: overlap_as_pat.clone(), span })
236-
.collect();
237-
rcx.tcx.emit_spanned_lint(
238-
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
239-
rcx.match_lint_level,
240-
this_span,
241-
OverlappingRangeEndpoints { overlap: overlaps, range: this_span },
242-
);
243-
};
244-
245-
// If two ranges overlapped, the split set will contain their intersection as a singleton.
246-
let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range());
247-
for overlap_range in split_int_ranges.clone() {
248-
if overlap_range.is_singleton() {
249-
let overlap: MaybeInfiniteInt = overlap_range.lo;
250-
// Ranges that look like `lo..=overlap`.
251-
let mut prefixes: SmallVec<[_; 1]> = Default::default();
252-
// Ranges that look like `overlap..=hi`.
253-
let mut suffixes: SmallVec<[_; 1]> = Default::default();
254-
// Iterate on patterns that contained `overlap`.
255-
for pat in column.iter() {
256-
let Constructor::IntRange(this_range) = pat.ctor() else { continue };
257-
let this_span = pat.data().unwrap().span;
258-
if this_range.is_singleton() {
259-
// Don't lint when one of the ranges is a singleton.
260-
continue;
261-
}
262-
if this_range.lo == overlap {
263-
// `this_range` looks like `overlap..=this_range.hi`; it overlaps with any
264-
// ranges that look like `lo..=overlap`.
265-
if !prefixes.is_empty() {
266-
emit_lint(overlap_range, this_span, &prefixes);
267-
}
268-
suffixes.push(this_span)
269-
} else if this_range.hi == overlap.plus_one() {
270-
// `this_range` looks like `this_range.lo..=overlap`; it overlaps with any
271-
// ranges that look like `overlap..=hi`.
272-
if !suffixes.is_empty() {
273-
emit_lint(overlap_range, this_span, &suffixes);
274-
}
275-
prefixes.push(this_span)
276-
}
277-
}
278-
}
279-
}
280-
} else {
281-
// Recurse into the fields.
282-
for ctor in set.present {
283-
for col in column.specialize(pcx, &ctor) {
284-
lint_overlapping_range_endpoints(cx, &col);
285-
}
286-
}
210+
let rcx = cx.tycx;
211+
for overlap in overlapping_range_endpoints {
212+
let overlap_as_pat = rcx.hoist_pat_range(&overlap.overlaps_on, overlap.pat.ty());
213+
let overlaps: Vec<_> = overlap
214+
.overlaps_with
215+
.iter()
216+
.map(|pat| pat.data().unwrap().span)
217+
.map(|span| Overlap { range: overlap_as_pat.clone(), span })
218+
.collect();
219+
let pat_span = overlap.pat.data().unwrap().span;
220+
rcx.tcx.emit_spanned_lint(
221+
lint::builtin::OVERLAPPING_RANGE_ENDPOINTS,
222+
rcx.match_lint_level,
223+
pat_span,
224+
OverlappingRangeEndpoints { overlap: overlaps, range: pat_span },
225+
);
287226
}
288227
}

compiler/rustc_pattern_analysis/src/rustc.rs

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub type DeconstructedPat<'p, 'tcx> =
3232
crate::pat::DeconstructedPat<'p, RustcMatchCheckCtxt<'p, 'tcx>>;
3333
pub type MatchArm<'p, 'tcx> = crate::MatchArm<'p, RustcMatchCheckCtxt<'p, 'tcx>>;
3434
pub type MatchCtxt<'a, 'p, 'tcx> = crate::MatchCtxt<'a, 'p, RustcMatchCheckCtxt<'p, 'tcx>>;
35+
pub type OverlappingRanges<'p, 'tcx> =
36+
crate::usefulness::OverlappingRanges<'p, RustcMatchCheckCtxt<'p, 'tcx>>;
3537
pub(crate) type PlaceCtxt<'a, 'p, 'tcx> =
3638
crate::usefulness::PlaceCtxt<'a, 'p, RustcMatchCheckCtxt<'p, 'tcx>>;
3739
pub(crate) type SplitConstructorSet<'p, 'tcx> =

compiler/rustc_pattern_analysis/src/usefulness.rs

+121-22
Original file line numberDiff line numberDiff line change
@@ -712,10 +712,11 @@
712712
//! I (Nadrieril) prefer to put new tests in `ui/pattern/usefulness` unless there's a specific
713713
//! reason not to, for example if they crucially depend on a particular feature like `or_patterns`.
714714
715+
use rustc_index::bit_set::BitSet;
715716
use smallvec::{smallvec, SmallVec};
716717
use std::fmt;
717718

718-
use crate::constructor::{Constructor, ConstructorSet};
719+
use crate::constructor::{Constructor, ConstructorSet, IntRange};
719720
use crate::pat::{DeconstructedPat, WitnessPat};
720721
use crate::{Captures, MatchArm, MatchCtxt, TypeCx, TypedArena};
721722

@@ -911,6 +912,10 @@ struct MatrixRow<'p, Cx: TypeCx> {
911912
/// [`compute_exhaustiveness_and_usefulness`] if the arm is found to be useful.
912913
/// This is reset to `false` when specializing.
913914
useful: bool,
915+
/// Tracks which rows above this one have an intersection with this one, i.e. such that there is
916+
/// a value that matches both rows.
917+
/// FIXME: Because of relevancy we may miss some intersections.
918+
intersects: BitSet<usize>,
914919
}
915920

916921
impl<'p, Cx: TypeCx> MatrixRow<'p, Cx> {
@@ -938,6 +943,7 @@ impl<'p, Cx: TypeCx> MatrixRow<'p, Cx> {
938943
parent_row: self.parent_row,
939944
is_under_guard: self.is_under_guard,
940945
useful: false,
946+
intersects: BitSet::new_empty(0), // Initialized in `Matrix::expand_and_push`.
941947
})
942948
}
943949

@@ -955,6 +961,7 @@ impl<'p, Cx: TypeCx> MatrixRow<'p, Cx> {
955961
parent_row,
956962
is_under_guard: self.is_under_guard,
957963
useful: false,
964+
intersects: BitSet::new_empty(0), // Initialized in `Matrix::expand_and_push`.
958965
}
959966
}
960967
}
@@ -991,13 +998,15 @@ struct Matrix<'p, Cx: TypeCx> {
991998
impl<'p, Cx: TypeCx> Matrix<'p, Cx> {
992999
/// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively
9931000
/// expands it. Internal method, prefer [`Matrix::new`].
994-
fn expand_and_push(&mut self, row: MatrixRow<'p, Cx>) {
1001+
fn expand_and_push(&mut self, mut row: MatrixRow<'p, Cx>) {
9951002
if !row.is_empty() && row.head().is_or_pat() {
9961003
// Expand nested or-patterns.
997-
for new_row in row.expand_or_pat() {
1004+
for mut new_row in row.expand_or_pat() {
1005+
new_row.intersects = BitSet::new_empty(self.rows.len());
9981006
self.rows.push(new_row);
9991007
}
10001008
} else {
1009+
row.intersects = BitSet::new_empty(self.rows.len());
10011010
self.rows.push(row);
10021011
}
10031012
}
@@ -1022,6 +1031,7 @@ impl<'p, Cx: TypeCx> Matrix<'p, Cx> {
10221031
parent_row: row_id, // dummy, we won't read it
10231032
is_under_guard: arm.has_guard,
10241033
useful: false,
1034+
intersects: BitSet::new_empty(row_id),
10251035
};
10261036
matrix.expand_and_push(v);
10271037
}
@@ -1334,6 +1344,7 @@ impl<Cx: TypeCx> WitnessMatrix<Cx> {
13341344
fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
13351345
mcx: MatchCtxt<'a, 'p, Cx>,
13361346
matrix: &mut Matrix<'p, Cx>,
1347+
overlapping_range_endpoints: &mut Vec<OverlappingRanges<'p, Cx>>,
13371348
is_top_level: bool,
13381349
) -> WitnessMatrix<Cx> {
13391350
debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count()));
@@ -1348,21 +1359,19 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
13481359
let Some(ty) = matrix.head_ty(mcx) else {
13491360
// The base case: there are no columns in the matrix. We are morally pattern-matching on ().
13501361
// A row is useful iff it has no (unguarded) rows above it.
1351-
for row in matrix.rows_mut() {
1352-
// All rows are useful until they're not.
1353-
row.useful = true;
1354-
// When there's an unguarded row, the match is exhaustive and any subsequent row is not
1355-
// useful.
1356-
if !row.is_under_guard {
1357-
return WitnessMatrix::empty();
1358-
}
1362+
let mut useful = true; // Whether the next row is useful.
1363+
for (i, row) in matrix.rows_mut().enumerate() {
1364+
row.useful = useful;
1365+
row.intersects.insert_range(0..i);
1366+
// The next rows stays useful if this one is under a guard.
1367+
useful &= row.is_under_guard;
13591368
}
1360-
// No (unguarded) rows, so the match is not exhaustive. We return a new witness unless
1361-
// irrelevant.
1362-
return if matrix.wildcard_row.relevant {
1369+
return if useful && matrix.wildcard_row.relevant {
1370+
// The wildcard row is useful; the match is non-exhaustive.
13631371
WitnessMatrix::unit_witness()
13641372
} else {
1365-
// We choose to not report anything here; see at the top for details.
1373+
// Either the match is exhaustive, or we choose not to report anything because of
1374+
// relevancy. See at the top for details.
13661375
WitnessMatrix::empty()
13671376
};
13681377
};
@@ -1415,18 +1424,93 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
14151424
let ctor_is_relevant = matches!(ctor, Constructor::Missing) || missing_ctors.is_empty();
14161425
let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor, ctor_is_relevant);
14171426
let mut witnesses = ensure_sufficient_stack(|| {
1418-
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+
)
14191433
});
14201434

14211435
// Transform witnesses for `spec_matrix` into witnesses for `matrix`.
14221436
witnesses.apply_constructor(pcx, &missing_ctors, &ctor, report_individual_missing_ctors);
14231437
// Accumulate the found witnesses.
14241438
ret.extend(witnesses);
14251439

1426-
// A parent row is useful if any of its children is.
1427-
for child_row in spec_matrix.rows() {
1428-
let parent_row = &mut matrix.rows[child_row.parent_row];
1429-
parent_row.useful = parent_row.useful || child_row.useful;
1440+
for (child_row_id, child_row) in spec_matrix.rows().enumerate() {
1441+
let parent_row_id = child_row.parent_row;
1442+
let parent_row = &mut matrix.rows[parent_row_id];
1443+
// A parent row is useful if any of its children is.
1444+
parent_row.useful |= child_row.useful;
1445+
for child_intersects in child_row.intersects.iter() {
1446+
// Convert the intersecting ids into ids for the parent matrix.
1447+
let parent_intersects = spec_matrix.rows[child_intersects].parent_row;
1448+
debug!("child row {child_row_id} intersects with child row {child_intersects}");
1449+
debug!("parent row {parent_row_id} intersects with parent row {parent_intersects}");
1450+
if parent_intersects != parent_row_id {
1451+
// self-intersect can happen with or-patterns
1452+
parent_row.intersects.insert(parent_intersects);
1453+
}
1454+
}
1455+
}
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+
}
14301514
}
14311515
}
14321516

@@ -1452,13 +1536,22 @@ pub enum Usefulness<'p, Cx: TypeCx> {
14521536
Redundant,
14531537
}
14541538

1539+
/// Indicates whether or not a given arm is useful.
1540+
#[derive(Clone, Debug)]
1541+
pub struct OverlappingRanges<'p, Cx: TypeCx> {
1542+
pub pat: &'p DeconstructedPat<'p, Cx>,
1543+
pub overlaps_on: IntRange,
1544+
pub overlaps_with: Vec<&'p DeconstructedPat<'p, Cx>>,
1545+
}
1546+
14551547
/// The output of checking a match for exhaustiveness and arm usefulness.
14561548
pub struct UsefulnessReport<'p, Cx: TypeCx> {
14571549
/// For each arm of the input, whether that arm is useful after the arms above it.
14581550
pub arm_usefulness: Vec<(MatchArm<'p, Cx>, Usefulness<'p, Cx>)>,
14591551
/// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of
14601552
/// exhaustiveness.
14611553
pub non_exhaustiveness_witnesses: Vec<WitnessPat<Cx>>,
1554+
pub overlapping_range_endpoints: Vec<OverlappingRanges<'p, Cx>>,
14621555
}
14631556

14641557
/// Computes whether a match is exhaustive and which of its arms are useful.
@@ -1469,8 +1562,14 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
14691562
scrut_ty: Cx::Ty,
14701563
scrut_validity: ValidityConstraint,
14711564
) -> UsefulnessReport<'p, Cx> {
1565+
let mut overlapping_range_endpoints = Vec::new();
14721566
let mut matrix = Matrix::new(cx.wildcard_arena, arms, scrut_ty, scrut_validity);
1473-
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+
);
14741573

14751574
let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column();
14761575
let arm_usefulness: Vec<_> = arms
@@ -1487,5 +1586,5 @@ pub fn compute_match_usefulness<'p, Cx: TypeCx>(
14871586
(arm, usefulness)
14881587
})
14891588
.collect();
1490-
UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses }
1589+
UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses, overlapping_range_endpoints }
14911590
}

0 commit comments

Comments
 (0)