Skip to content

Commit 35fe75d

Browse files
committed
Make IntRange exclusive
1 parent feb769a commit 35fe75d

File tree

2 files changed

+65
-57
lines changed

2 files changed

+65
-57
lines changed

Diff for: compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs

+64-56
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub(crate) enum MaybeInfiniteInt {
101101
NegInfinity,
102102
/// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite`.
103103
Finite(u128),
104-
/// The integer after `u128::MAX`. Used when we switch to exclusive ranges in `IntRange::split`.
104+
/// The integer after `u128::MAX`. We need it to represent `x..=u128::MAX` as an exclusive range.
105105
JustAfterMax,
106106
PosInfinity,
107107
}
@@ -140,8 +140,11 @@ impl MaybeInfiniteInt {
140140
PatRangeBoundary::PosInfinity => PosInfinity,
141141
}
142142
}
143+
143144
/// Used only for diagnostics.
144-
/// This could change from finite to infinite if we got `usize::MAX+1` after range splitting.
145+
/// Note: it is possible to get `isize/usize::MAX+1` here, as explained in the doc for
146+
/// [`IntRange::split`]. This cannot be represented as a `Const`, so we represent it with
147+
/// `PosInfinity`.
145148
fn to_diagnostic_pat_range_bdy<'tcx>(
146149
self,
147150
ty: Ty<'tcx>,
@@ -168,20 +171,19 @@ impl MaybeInfiniteInt {
168171
}
169172
}
170173

171-
fn is_finite(self) -> bool {
172-
matches!(self, Finite(_))
173-
}
174-
fn minus_one(self) -> Self {
174+
/// Note: this will not turn a finite value into an infinite one or vice-versa.
175+
pub(crate) fn minus_one(self) -> Self {
175176
match self {
176177
Finite(n) => match n.checked_sub(1) {
177178
Some(m) => Finite(m),
178-
None => NegInfinity,
179+
None => bug!(),
179180
},
180181
JustAfterMax => Finite(u128::MAX),
181182
x => x,
182183
}
183184
}
184-
fn plus_one(self) -> Self {
185+
/// Note: this will not turn a finite value into an infinite one or vice-versa.
186+
pub(crate) fn plus_one(self) -> Self {
185187
match self {
186188
Finite(n) => match n.checked_add(1) {
187189
Some(m) => Finite(m),
@@ -193,18 +195,15 @@ impl MaybeInfiniteInt {
193195
}
194196
}
195197

196-
/// An inclusive interval, used for precise integer exhaustiveness checking. `IntRange`s always
198+
/// An exclusive interval, used for precise integer exhaustiveness checking. `IntRange`s always
197199
/// store a contiguous range.
198200
///
199201
/// `IntRange` is never used to encode an empty range or a "range" that wraps around the (offset)
200-
/// space: i.e., `range.lo <= range.hi`.
201-
///
202-
/// Note: the range can be `NegInfinity..=NegInfinity` or `PosInfinity..=PosInfinity` to represent
203-
/// the values before `isize::MIN` and after `isize::MAX`/`usize::MAX`.
202+
/// space: i.e., `range.lo < range.hi`.
204203
#[derive(Clone, Copy, PartialEq, Eq)]
205204
pub(crate) struct IntRange {
206-
pub(crate) lo: MaybeInfiniteInt,
207-
pub(crate) hi: MaybeInfiniteInt,
205+
pub(crate) lo: MaybeInfiniteInt, // Must not be `PosInfinity`.
206+
pub(crate) hi: MaybeInfiniteInt, // Must not be `NegInfinity`.
208207
}
209208

210209
impl IntRange {
@@ -215,23 +214,25 @@ impl IntRange {
215214

216215
/// Best effort; will not know that e.g. `255u8..` is a singleton.
217216
pub(super) fn is_singleton(&self) -> bool {
218-
self.lo == self.hi && self.lo.is_finite()
217+
// Since `lo` and `hi` can't be the same `Infinity` and `plus_one` never changes from finite
218+
// to infinite, this correctly only detects ranges that contain exacly one `Finite(x)`.
219+
self.lo.plus_one() == self.hi
219220
}
220221

221222
#[inline]
222223
fn from_bits<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, bits: u128) -> IntRange {
223224
let x = MaybeInfiniteInt::new_finite(tcx, ty, bits);
224-
IntRange { lo: x, hi: x }
225+
IntRange { lo: x, hi: x.plus_one() }
225226
}
226227

227228
#[inline]
228229
fn from_range(lo: MaybeInfiniteInt, mut hi: MaybeInfiniteInt, end: RangeEnd) -> IntRange {
229-
if end == RangeEnd::Excluded {
230-
hi = hi.minus_one();
230+
if end == RangeEnd::Included {
231+
hi = hi.plus_one();
231232
}
232-
if lo > hi {
233+
if lo >= hi {
233234
// This should have been caught earlier by E0030.
234-
bug!("malformed range pattern: {lo:?}..={hi:?}");
235+
bug!("malformed range pattern: {lo:?}..{hi:?}");
235236
}
236237
IntRange { lo, hi }
237238
}
@@ -241,7 +242,7 @@ impl IntRange {
241242
}
242243

243244
fn intersection(&self, other: &Self) -> Option<Self> {
244-
if self.lo <= other.hi && other.lo <= self.hi {
245+
if self.lo < other.hi && other.lo < self.hi {
245246
Some(IntRange { lo: max(self.lo, other.lo), hi: min(self.hi, other.hi) })
246247
} else {
247248
None
@@ -275,38 +276,45 @@ impl IntRange {
275276
/// ```
276277
/// where each sequence of dashes is an output range, and dashes outside parentheses are marked
277278
/// as `Presence::Missing`.
279+
///
280+
/// ## `isize`/`usize`
281+
///
282+
/// Whereas a wildcard of type `i32` stands for the range `i32::MIN..=i32::MAX`, a `usize`
283+
/// wildcard stands for `0..PosInfinity` and a `isize` wildcard stands for
284+
/// `NegInfinity..PosInfinity`. In other words, as far as `IntRange` is concerned, there are
285+
/// values before `isize::MIN` and after `usize::MAX`/`isize::MAX`.
286+
/// This is to avoid e.g. `0..(u32::MAX as usize)` from being exhaustive on one architecture and
287+
/// not others. See discussions around the `precise_pointer_size_matching` feature for more
288+
/// details.
289+
///
290+
/// These infinities affect splitting subtly: it is possible to get `NegInfinity..0` and
291+
/// `usize::MAX+1..PosInfinity` in the output. Diagnostics must be careful to handle these
292+
/// fictitious ranges sensibly.
278293
fn split(
279294
&self,
280295
column_ranges: impl Iterator<Item = IntRange>,
281296
) -> impl Iterator<Item = (Presence, IntRange)> {
282-
// Make the range into an exclusive range.
283-
fn unpack_intrange(range: IntRange) -> [MaybeInfiniteInt; 2] {
284-
[range.lo, range.hi.plus_one()]
285-
}
286-
287297
// The boundaries of ranges in `column_ranges` intersected with `self`.
288298
// We do parenthesis matching for input ranges. A boundary counts as +1 if it starts
289299
// a range and -1 if it ends it. When the count is > 0 between two boundaries, we
290300
// are within an input range.
291301
let mut boundaries: Vec<(MaybeInfiniteInt, isize)> = column_ranges
292302
.filter_map(|r| self.intersection(&r))
293-
.map(unpack_intrange)
294-
.flat_map(|[lo, hi]| [(lo, 1), (hi, -1)])
303+
.flat_map(|r| [(r.lo, 1), (r.hi, -1)])
295304
.collect();
296305
// We sort by boundary, and for each boundary we sort the "closing parentheses" first. The
297306
// order of +1/-1 for a same boundary value is actually irrelevant, because we only look at
298307
// the accumulated count between distinct boundary values.
299308
boundaries.sort_unstable();
300309

301-
let [self_start, self_end] = unpack_intrange(*self);
302310
// Accumulate parenthesis counts.
303311
let mut paren_counter = 0isize;
304312
// Gather pairs of adjacent boundaries.
305-
let mut prev_bdy = self_start;
313+
let mut prev_bdy = self.lo;
306314
boundaries
307315
.into_iter()
308316
// End with the end of the range. The count is ignored.
309-
.chain(once((self_end, 0)))
317+
.chain(once((self.hi, 0)))
310318
// List pairs of adjacent boundaries and the count between them.
311319
.map(move |(bdy, delta)| {
312320
// `delta` affects the count as we cross `bdy`, so the relevant count between
@@ -322,21 +330,22 @@ impl IntRange {
322330
.map(move |(prev_bdy, paren_count, bdy)| {
323331
use Presence::*;
324332
let presence = if paren_count > 0 { Seen } else { Unseen };
325-
// Turn back into an inclusive range.
326-
let range = IntRange::from_range(prev_bdy, bdy, RangeEnd::Excluded);
333+
let range = IntRange { lo: prev_bdy, hi: bdy };
327334
(presence, range)
328335
})
329336
}
330337

331-
/// Whether the range denotes the values before `isize::MIN` or the values after
332-
/// `usize::MAX`/`isize::MAX`.
338+
/// Whether the range denotes the fictitious values before `isize::MIN` or after
339+
/// `usize::MAX`/`isize::MAX` (see doc of [`IntRange::split`] for why these exist).
333340
pub(crate) fn is_beyond_boundaries<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool {
334-
// First check if we are usize/isize to avoid unnecessary `to_diagnostic_pat_range_bdy`.
335341
ty.is_ptr_sized_integral() && !tcx.features().precise_pointer_size_matching && {
342+
// The two invalid ranges are `NegInfinity..isize::MIN` (represented as
343+
// `NegInfinity..0`), and `{u,i}size::MAX+1..PosInfinity`. `to_diagnostic_pat_range_bdy`
344+
// converts `MAX+1` to `PosInfinity`, and we couldn't have `PosInfinity` in `self.lo`
345+
// otherwise.
336346
let lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx);
337-
let hi = self.hi.to_diagnostic_pat_range_bdy(ty, tcx);
338347
matches!(lo, PatRangeBoundary::PosInfinity)
339-
|| matches!(hi, PatRangeBoundary::NegInfinity)
348+
|| matches!(self.hi, MaybeInfiniteInt::Finite(0))
340349
}
341350
}
342351
/// Only used for displaying the range.
@@ -348,28 +357,27 @@ impl IntRange {
348357
let value = lo.as_finite().unwrap();
349358
PatKind::Constant { value }
350359
} else {
360+
// We convert to an inclusive range for diagnostics.
361+
let mut end = RangeEnd::Included;
351362
let mut lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx);
352-
let mut hi = self.hi.to_diagnostic_pat_range_bdy(ty, tcx);
353-
let end = if hi.is_finite() {
354-
RangeEnd::Included
355-
} else {
356-
// `0..=` isn't a valid pattern.
357-
RangeEnd::Excluded
358-
};
359-
if matches!(hi, PatRangeBoundary::NegInfinity) {
360-
// The range denotes the values before `isize::MIN`.
361-
let c = ty.numeric_min_val(tcx).unwrap();
362-
let value = mir::Const::from_ty_const(c, tcx);
363-
hi = PatRangeBoundary::Finite(value);
364-
}
365363
if matches!(lo, PatRangeBoundary::PosInfinity) {
366-
// The range denotes the values after `usize::MAX`/`isize::MAX`.
367-
// We represent this as `usize::MAX..` which is slightly incorrect but probably
368-
// clear enough.
364+
// The only reason to get `PosInfinity` here is the special case where
365+
// `to_diagnostic_pat_range_bdy` found `{u,i}size::MAX+1`. So the range denotes the
366+
// fictitious values after `{u,i}size::MAX` (see [`IntRange::split`] for why we do
367+
// this). We show this to the user as `usize::MAX..` which is slightly incorrect but
368+
// probably clear enough.
369369
let c = ty.numeric_max_val(tcx).unwrap();
370370
let value = mir::Const::from_ty_const(c, tcx);
371371
lo = PatRangeBoundary::Finite(value);
372372
}
373+
let hi = if matches!(self.hi, MaybeInfiniteInt::Finite(0)) {
374+
// The range encodes `..ty::MIN`, so we can't convert it to an inclusive range.
375+
end = RangeEnd::Excluded;
376+
self.hi
377+
} else {
378+
self.hi.minus_one()
379+
};
380+
let hi = hi.to_diagnostic_pat_range_bdy(ty, tcx);
373381
PatKind::Range(Box::new(PatRange { lo, hi, end, ty }))
374382
};
375383

@@ -384,7 +392,7 @@ impl fmt::Debug for IntRange {
384392
if let Finite(lo) = self.lo {
385393
write!(f, "{lo}")?;
386394
}
387-
write!(f, "{}", RangeEnd::Included)?;
395+
write!(f, "{}", RangeEnd::Excluded)?;
388396
if let Finite(hi) = self.hi {
389397
write!(f, "{hi}")?;
390398
}

Diff for: compiler/rustc_mir_build/src/thir/pattern/usefulness.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>(
10521052
emit_lint(overlap_range, this_span, &prefixes);
10531053
}
10541054
suffixes.push(this_span)
1055-
} else if this_range.hi == overlap {
1055+
} else if this_range.hi == overlap.plus_one() {
10561056
// `this_range` looks like `this_range.lo..=overlap`; it overlaps with any
10571057
// ranges that look like `overlap..=hi`.
10581058
if !suffixes.is_empty() {

0 commit comments

Comments
 (0)