Skip to content

Commit 3ea92c0

Browse files
committed
Make IntRange exclusive
1 parent 4247b82 commit 3ea92c0

File tree

2 files changed

+65
-57
lines changed

2 files changed

+65
-57
lines changed

compiler/rustc_middle/src/thir.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -927,9 +927,12 @@ impl<'tcx> fmt::Display for PatRange<'tcx> {
927927
if let PatRangeBoundary::Finite(value) = &self.lo {
928928
write!(f, "{value}")?;
929929
}
930-
write!(f, "{}", self.end)?;
931930
if let PatRangeBoundary::Finite(value) = &self.hi {
931+
write!(f, "{}", self.end)?;
932932
write!(f, "{value}")?;
933+
} else {
934+
// `0..` is parsed as an inclusive range, we must display it correctly.
935+
write!(f, "..")?;
933936
}
934937
Ok(())
935938
}

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

+61-56
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ enum MaybeInfiniteInt {
103103
NegInfinity,
104104
/// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite`.
105105
Finite(u128),
106-
/// The integer after `u128::MAX`. Used when we switch to exclusive ranges in `IntRange::split`.
106+
/// The integer after `u128::MAX`. We need it to represent `x..=u128::MAX` as an exclusive range.
107107
JustAfterMax,
108108
PosInfinity,
109109
}
@@ -142,8 +142,11 @@ impl MaybeInfiniteInt {
142142
PatRangeBoundary::PosInfinity => PosInfinity,
143143
}
144144
}
145+
145146
/// Used only for diagnostics.
146-
/// This could change from finite to infinite if we got `usize::MAX+1` after range splitting.
147+
/// Note: it is possible to get `isize/usize::MAX+1` here, as explained in the doc for
148+
/// [`IntRange::split`]. This cannot be represented as a `Const`, so we represent it with
149+
/// `PosInfinity`.
147150
fn to_diagnostic_pat_range_bdy<'tcx>(
148151
self,
149152
ty: Ty<'tcx>,
@@ -170,19 +173,18 @@ impl MaybeInfiniteInt {
170173
}
171174
}
172175

173-
fn is_finite(self) -> bool {
174-
matches!(self, Finite(_))
175-
}
176+
/// Note: this will not turn a finite value into an infinite one or vice-versa.
176177
fn minus_one(self) -> Self {
177178
match self {
178179
Finite(n) => match n.checked_sub(1) {
179180
Some(m) => Finite(m),
180-
None => NegInfinity,
181+
None => bug!(),
181182
},
182183
JustAfterMax => Finite(u128::MAX),
183184
x => x,
184185
}
185186
}
187+
/// Note: this will not turn a finite value into an infinite one or vice-versa.
186188
fn plus_one(self) -> Self {
187189
match self {
188190
Finite(n) => match n.checked_add(1) {
@@ -195,18 +197,15 @@ impl MaybeInfiniteInt {
195197
}
196198
}
197199

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

212211
impl IntRange {
@@ -217,23 +216,23 @@ impl IntRange {
217216

218217
/// Best effort; will not know that e.g. `255u8..` is a singleton.
219218
fn is_singleton(&self) -> bool {
220-
self.lo == self.hi && self.lo.is_finite()
219+
self.lo.plus_one() == self.hi
221220
}
222221

223222
#[inline]
224223
fn from_bits<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, bits: u128) -> IntRange {
225224
let x = MaybeInfiniteInt::new_finite(tcx, ty, bits);
226-
IntRange { lo: x, hi: x }
225+
IntRange { lo: x, hi: x.plus_one() }
227226
}
228227

229228
#[inline]
230229
fn from_range(lo: MaybeInfiniteInt, mut hi: MaybeInfiniteInt, end: RangeEnd) -> IntRange {
231-
if end == RangeEnd::Excluded {
232-
hi = hi.minus_one();
230+
if end == RangeEnd::Included {
231+
hi = hi.plus_one();
233232
}
234-
if lo > hi {
233+
if lo >= hi {
235234
// This should have been caught earlier by E0030.
236-
bug!("malformed range pattern: {lo:?}..={hi:?}");
235+
bug!("malformed range pattern: {lo:?}..{hi:?}");
237236
}
238237
IntRange { lo, hi }
239238
}
@@ -243,7 +242,7 @@ impl IntRange {
243242
}
244243

245244
fn intersection(&self, other: &Self) -> Option<Self> {
246-
if self.lo <= other.hi && other.lo <= self.hi {
245+
if self.lo < other.hi && other.lo < self.hi {
247246
Some(IntRange { lo: max(self.lo, other.lo), hi: min(self.hi, other.hi) })
248247
} else {
249248
None
@@ -262,8 +261,7 @@ impl IntRange {
262261
// `true` in the following cases:
263262
// 1 ------- // 1 -------
264263
// 2 -------- // 2 -------
265-
((self.lo == other.hi && self.lo.is_finite())
266-
|| (self.hi == other.lo && self.hi.is_finite()))
264+
((self.lo.plus_one() == other.hi) || (other.lo.plus_one() == self.hi))
267265
&& !self.is_singleton()
268266
&& !other.is_singleton()
269267
}
@@ -295,38 +293,45 @@ impl IntRange {
295293
/// ```
296294
/// where each sequence of dashes is an output range, and dashes outside parentheses are marked
297295
/// as `Presence::Missing`.
296+
///
297+
/// ## `isize`/`usize`
298+
///
299+
/// Whereas a wildcard of type `i32` stands for the range `i32::MIN..=i32::MAX`, a `usize`
300+
/// wildcard stands for `0..PosInfinity` and a `isize` wildcard stands for
301+
/// `NegInfinity..PosInfinity`. In other words, as far as `IntRange` is concerned, there are
302+
/// values before `isize::MIN` and after `usize::MAX`/`isize::MAX`.
303+
/// This is to avoid e.g. `0..(u32::MAX as usize)` from being exhaustive on one architecture and
304+
/// not others. See discussions around the `precise_pointer_size_matching` feature for more
305+
/// details.
306+
///
307+
/// These infinities affect splitting subtly: it is possible to get `NegInfinity..0` and
308+
/// `usize::MAX+1..PosInfinity` in the output. Diagnostics must be careful to handle these
309+
/// fictitious ranges sensibly.
298310
fn split(
299311
&self,
300312
column_ranges: impl Iterator<Item = IntRange>,
301313
) -> impl Iterator<Item = (Presence, IntRange)> {
302-
// Make the range into an exclusive range.
303-
fn unpack_intrange(range: IntRange) -> [MaybeInfiniteInt; 2] {
304-
[range.lo, range.hi.plus_one()]
305-
}
306-
307314
// The boundaries of ranges in `column_ranges` intersected with `self`.
308315
// We do parenthesis matching for input ranges. A boundary counts as +1 if it starts
309316
// a range and -1 if it ends it. When the count is > 0 between two boundaries, we
310317
// are within an input range.
311318
let mut boundaries: Vec<(MaybeInfiniteInt, isize)> = column_ranges
312319
.filter_map(|r| self.intersection(&r))
313-
.map(unpack_intrange)
314-
.flat_map(|[lo, hi]| [(lo, 1), (hi, -1)])
320+
.flat_map(|r| [(r.lo, 1), (r.hi, -1)])
315321
.collect();
316322
// We sort by boundary, and for each boundary we sort the "closing parentheses" first. The
317323
// order of +1/-1 for a same boundary value is actually irrelevant, because we only look at
318324
// the accumulated count between distinct boundary values.
319325
boundaries.sort_unstable();
320326

321-
let [self_start, self_end] = unpack_intrange(*self);
322327
// Accumulate parenthesis counts.
323328
let mut paren_counter = 0isize;
324329
// Gather pairs of adjacent boundaries.
325-
let mut prev_bdy = self_start;
330+
let mut prev_bdy = self.lo;
326331
boundaries
327332
.into_iter()
328333
// End with the end of the range. The count is ignored.
329-
.chain(once((self_end, 0)))
334+
.chain(once((self.hi, 0)))
330335
// List pairs of adjacent boundaries and the count between them.
331336
.map(move |(bdy, delta)| {
332337
// `delta` affects the count as we cross `bdy`, so the relevant count between
@@ -342,21 +347,22 @@ impl IntRange {
342347
.map(move |(prev_bdy, paren_count, bdy)| {
343348
use Presence::*;
344349
let presence = if paren_count > 0 { Seen } else { Unseen };
345-
// Turn back into an inclusive range.
346-
let range = IntRange::from_range(prev_bdy, bdy, RangeEnd::Excluded);
350+
let range = IntRange { lo: prev_bdy, hi: bdy };
347351
(presence, range)
348352
})
349353
}
350354

351-
/// Whether the range denotes the values before `isize::MIN` or the values after
352-
/// `usize::MAX`/`isize::MAX`.
355+
/// Whether the range denotes the fictitious values before `isize::MIN` or after
356+
/// `usize::MAX`/`isize::MAX` (see doc of [`IntRange::split`] for why these exist).
353357
pub(crate) fn is_beyond_boundaries<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool {
354-
// First check if we are usize/isize to avoid unnecessary `to_diagnostic_pat_range_bdy`.
355358
ty.is_ptr_sized_integral() && !tcx.features().precise_pointer_size_matching && {
359+
// The two invalid ranges are `NegInfinity..isize::MIN` (represented as
360+
// `NegInfinity..0`), and `{u,i}size::MAX+1..PosInfinity`. `to_diagnostic_pat_range_bdy`
361+
// converts `MAX+1` to `PosInfinity`, and we couldn't have `PosInfinity` in `self.lo`
362+
// otherwise.
356363
let lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx);
357-
let hi = self.hi.to_diagnostic_pat_range_bdy(ty, tcx);
358364
matches!(lo, PatRangeBoundary::PosInfinity)
359-
|| matches!(hi, PatRangeBoundary::NegInfinity)
365+
|| matches!(self.hi, MaybeInfiniteInt::Finite(0))
360366
}
361367
}
362368
/// Only used for displaying the range.
@@ -368,28 +374,27 @@ impl IntRange {
368374
let value = lo.as_finite().unwrap();
369375
PatKind::Constant { value }
370376
} else {
377+
// We convert to an inclusive range for diagnostics.
378+
let mut end = RangeEnd::Included;
371379
let mut lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx);
372-
let mut hi = self.hi.to_diagnostic_pat_range_bdy(ty, tcx);
373-
let end = if hi.is_finite() {
374-
RangeEnd::Included
375-
} else {
376-
// `0..=` isn't a valid pattern.
377-
RangeEnd::Excluded
378-
};
379-
if matches!(hi, PatRangeBoundary::NegInfinity) {
380-
// The range denotes the values before `isize::MIN`.
381-
let c = ty.numeric_min_val(tcx).unwrap();
382-
let value = mir::Const::from_ty_const(c, tcx);
383-
hi = PatRangeBoundary::Finite(value);
384-
}
385380
if matches!(lo, PatRangeBoundary::PosInfinity) {
386-
// The range denotes the values after `usize::MAX`/`isize::MAX`.
387-
// We represent this as `usize::MAX..` which is slightly incorrect but probably
388-
// clear enough.
381+
// The only reason to get `PosInfinity` here is the special case where
382+
// `to_diagnostic_pat_range_bdy` found `{u,i}size::MAX+1`. So the range denotes the
383+
// fictitious values after `{u,i}size::MAX` (see [`IntRange::split`] for why we do
384+
// this). We show this to the user as `usize::MAX..` which is slightly incorrect but
385+
// probably clear enough.
389386
let c = ty.numeric_max_val(tcx).unwrap();
390387
let value = mir::Const::from_ty_const(c, tcx);
391388
lo = PatRangeBoundary::Finite(value);
392389
}
390+
let hi = if matches!(self.hi, MaybeInfiniteInt::Finite(0)) {
391+
// The range encodes `..ty::MIN`, so we can't convert it to an inclusive range.
392+
end = RangeEnd::Excluded;
393+
self.hi
394+
} else {
395+
self.hi.minus_one()
396+
};
397+
let hi = hi.to_diagnostic_pat_range_bdy(ty, tcx);
393398
PatKind::Range(Box::new(PatRange { lo, hi, end, ty }))
394399
};
395400

@@ -449,7 +454,7 @@ impl fmt::Debug for IntRange {
449454
if let Finite(lo) = self.lo {
450455
write!(f, "{lo}")?;
451456
}
452-
write!(f, "{}", RangeEnd::Included)?;
457+
write!(f, "{}", RangeEnd::Excluded)?;
453458
if let Finite(hi) = self.hi {
454459
write!(f, "{hi}")?;
455460
}

0 commit comments

Comments
 (0)