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

Commit 5a24b2c

Browse files
committed
Factor out SplitIntRange used for integer range splitting
1 parent 42b77c7 commit 5a24b2c

File tree

1 file changed

+110
-89
lines changed

1 file changed

+110
-89
lines changed

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

Lines changed: 110 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use rustc_target::abi::{Integer, Size, VariantIdx};
2424

2525
use smallvec::{smallvec, SmallVec};
2626
use std::cmp::{self, max, min, Ordering};
27-
use std::iter::IntoIterator;
27+
use std::iter::{once, IntoIterator};
2828
use std::ops::RangeInclusive;
2929

3030
/// An inclusive interval, used for precise integer exhaustiveness checking.
@@ -183,77 +183,24 @@ impl IntRange {
183183
Pat { ty, span: DUMMY_SP, kind: Box::new(kind) }
184184
}
185185

186-
/// For exhaustive integer matching, some constructors are grouped within other constructors
187-
/// (namely integer typed values are grouped within ranges). However, when specialising these
188-
/// constructors, we want to be specialising for the underlying constructors (the integers), not
189-
/// the groups (the ranges). Thus we need to split the groups up. Splitting them up naïvely would
190-
/// mean creating a separate constructor for every single value in the range, which is clearly
191-
/// impractical. However, observe that for some ranges of integers, the specialisation will be
192-
/// identical across all values in that range (i.e., there are equivalence classes of ranges of
193-
/// constructors based on their `U(S(c, P), S(c, p))` outcome). These classes are grouped by
194-
/// the patterns that apply to them (in the matrix `P`). We can split the range whenever the
195-
/// patterns that apply to that range (specifically: the patterns that *intersect* with that range)
196-
/// change.
197-
/// Our solution, therefore, is to split the range constructor into subranges at every single point
198-
/// the group of intersecting patterns changes (using the method described below).
199-
/// And voilà! We're testing precisely those ranges that we need to, without any exhaustive matching
200-
/// on actual integers. The nice thing about this is that the number of subranges is linear in the
201-
/// number of rows in the matrix (i.e., the number of cases in the `match` statement), so we don't
202-
/// need to be worried about matching over gargantuan ranges.
203-
///
204-
/// Essentially, given the first column of a matrix representing ranges, looking like the following:
205-
///
206-
/// |------| |----------| |-------| ||
207-
/// |-------| |-------| |----| ||
208-
/// |---------|
209-
///
210-
/// We split the ranges up into equivalence classes so the ranges are no longer overlapping:
211-
///
212-
/// |--|--|||-||||--||---|||-------| |-|||| ||
213-
///
214-
/// The logic for determining how to split the ranges is fairly straightforward: we calculate
215-
/// boundaries for each interval range, sort them, then create constructors for each new interval
216-
/// between every pair of boundary points. (This essentially sums up to performing the intuitive
217-
/// merging operation depicted above.)
186+
/// Split this range, as described at the top of the file.
218187
fn split<'p, 'tcx>(
219188
&self,
220189
pcx: PatCtxt<'_, 'p, 'tcx>,
221190
hir_id: Option<HirId>,
222191
) -> SmallVec<[Constructor<'tcx>; 1]> {
223-
/// Represents a border between 2 integers. Because the intervals spanning borders
224-
/// must be able to cover every integer, we need to be able to represent
225-
/// 2^128 + 1 such borders.
226-
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
227-
enum Border {
228-
JustBefore(u128),
229-
AfterMax,
230-
}
231-
232-
// A function for extracting the borders of an integer interval.
233-
fn range_borders(r: IntRange) -> impl Iterator<Item = Border> {
234-
let (lo, hi) = r.range.into_inner();
235-
let from = Border::JustBefore(lo);
236-
let to = match hi.checked_add(1) {
237-
Some(m) => Border::JustBefore(m),
238-
None => Border::AfterMax,
239-
};
240-
vec![from, to].into_iter()
241-
}
242-
243-
// Collect the span and range of all the intersecting ranges to lint on likely
244-
// incorrect range patterns. (#63987)
192+
// We collect the span and range of all the intersecting ranges to lint on likely incorrect
193+
// range patterns. (#63987)
245194
let mut overlaps = vec![];
195+
let mut split_range = SplitIntRange::new(self.clone());
246196
let row_len = pcx.matrix.column_count().unwrap_or(0);
247-
// `borders` is the set of borders between equivalence classes: each equivalence
248-
// class lies between 2 borders.
249-
let row_borders = pcx
197+
let intranges = pcx
250198
.matrix
251199
.head_ctors_and_spans(pcx.cx)
252-
.filter_map(|(ctor, span)| Some((ctor.as_int_range()?, span)))
253-
.filter_map(|(range, span)| {
254-
let intersection = self.intersection(&range);
255-
let should_lint = self.suspicious_intersection(&range);
256-
if let (Some(range), 1, true) = (&intersection, row_len, should_lint) {
200+
.filter_map(|(ctor, span)| Some((ctor.as_int_range()?, span)));
201+
let intranges = intranges.inspect(|(range, span)| {
202+
if let Some(intersection) = self.intersection(&range) {
203+
if row_len == 1 && self.suspicious_intersection(&range) {
257204
// FIXME: for now, only check for overlapping ranges on simple range
258205
// patterns. Otherwise with the current logic the following is detected
259206
// as overlapping:
@@ -264,36 +211,15 @@ impl IntRange {
264211
// _ => {}
265212
// }
266213
// ```
267-
overlaps.push((range.clone(), span));
214+
overlaps.push((intersection.clone(), *span));
268215
}
269-
intersection
270-
})
271-
.flat_map(range_borders);
272-
let self_borders = range_borders(self.clone());
273-
let mut borders: Vec<_> = row_borders.chain(self_borders).collect();
274-
borders.sort_unstable();
216+
}
217+
});
218+
split_range.split(intranges.map(|(range, _)| range).cloned());
275219

276220
self.lint_overlapping_range_endpoints(pcx, hir_id, overlaps);
277221

278-
// We're going to iterate through every adjacent pair of borders, making sure that
279-
// each represents an interval of nonnegative length, and convert each such
280-
// interval into a constructor.
281-
borders
282-
.array_windows()
283-
.filter_map(|&pair| match pair {
284-
[Border::JustBefore(n), Border::JustBefore(m)] => {
285-
if n < m {
286-
Some(n..=(m - 1))
287-
} else {
288-
None
289-
}
290-
}
291-
[Border::JustBefore(n), Border::AfterMax] => Some(n..=u128::MAX),
292-
[Border::AfterMax, _] => None,
293-
})
294-
.map(|range| IntRange { range })
295-
.map(IntRange)
296-
.collect()
222+
split_range.iter().map(IntRange).collect()
297223
}
298224

299225
fn lint_overlapping_range_endpoints(
@@ -339,6 +265,101 @@ impl IntRange {
339265
}
340266
}
341267

268+
/// Represents a border between 2 integers. Because the intervals spanning borders must be able to
269+
/// cover every integer, we need to be able to represent 2^128 + 1 such borders.
270+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
271+
enum IntBorder {
272+
JustBefore(u128),
273+
AfterMax,
274+
}
275+
276+
/// A range of integers that is partitioned into disjoint subranges.
277+
///
278+
/// This is fed an input of multiple ranges, and returns an output that covers the union of the
279+
/// inputs but is split so that an output range only intersects an input range by being a subrange
280+
/// of it. No output range straddles the boundary of one of the inputs. This does constructor
281+
/// splitting for integer ranges as explained at the top of the file.
282+
///
283+
/// The following input:
284+
/// ```
285+
/// |-------------------------| // `self`
286+
/// |------| |----------| |----|
287+
/// |-------| |-------|
288+
/// ```
289+
/// would be iterated over as follows:
290+
/// ```
291+
/// ||---|--||-|---|---|---|--|
292+
/// ```
293+
#[derive(Debug, Clone)]
294+
struct SplitIntRange {
295+
/// The range we are splitting
296+
range: IntRange,
297+
/// The borders of ranges we have seen. They are all contained within `range`. This is kept
298+
/// sorted.
299+
borders: Vec<IntBorder>,
300+
}
301+
302+
impl SplitIntRange {
303+
fn new(r: IntRange) -> Self {
304+
SplitIntRange { range: r.clone(), borders: Vec::new() }
305+
}
306+
307+
/// Internal use
308+
fn to_borders(r: IntRange) -> [IntBorder; 2] {
309+
use IntBorder::*;
310+
let (lo, hi) = r.boundaries();
311+
let lo = JustBefore(lo);
312+
let hi = match hi.checked_add(1) {
313+
Some(m) => JustBefore(m),
314+
None => AfterMax,
315+
};
316+
[lo, hi]
317+
}
318+
319+
/// Add ranges relative to which we split.
320+
fn split(&mut self, ranges: impl Iterator<Item = IntRange>) {
321+
let this_range = &self.range;
322+
let included_ranges = ranges.filter_map(|r| this_range.intersection(&r));
323+
let included_borders = included_ranges.flat_map(|r| {
324+
let borders = Self::to_borders(r);
325+
once(borders[0]).chain(once(borders[1]))
326+
});
327+
self.borders.extend(included_borders);
328+
self.borders.sort_unstable();
329+
}
330+
331+
/// Iterate over the contained ranges.
332+
fn iter<'a>(&'a self) -> impl Iterator<Item = IntRange> + Captures<'a> {
333+
use IntBorder::*;
334+
335+
let self_range = Self::to_borders(self.range.clone());
336+
// Start with the start of the range.
337+
let mut prev_border = self_range[0];
338+
self.borders
339+
.iter()
340+
.copied()
341+
// End with the end of the range.
342+
.chain(once(self_range[1]))
343+
// List pairs of adjacent borders.
344+
.map(move |border| {
345+
let ret = (prev_border, border);
346+
prev_border = border;
347+
ret
348+
})
349+
// Skip duplicates.
350+
.filter(|(prev_border, border)| prev_border != border)
351+
// Finally, convert to ranges.
352+
.map(|(prev_border, border)| {
353+
let range = match (prev_border, border) {
354+
(JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1),
355+
(JustBefore(n), AfterMax) => n..=u128::MAX,
356+
_ => unreachable!(), // Ruled out by the sorting and filtering we did
357+
};
358+
IntRange { range }
359+
})
360+
}
361+
}
362+
342363
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
343364
enum SliceKind {
344365
/// Patterns of length `n` (`[x, y]`).

0 commit comments

Comments
 (0)