Skip to content

Commit 7521f2e

Browse files
committed
Propagate half-open ranges through exhaustiveness checking
1 parent a37deb0 commit 7521f2e

File tree

1 file changed

+153
-100
lines changed

1 file changed

+153
-100
lines changed

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

+153-100
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ use rustc_span::{Span, DUMMY_SP};
6464
use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};
6565

6666
use self::Constructor::*;
67+
use self::MaybeInfiniteInt::*;
6768
use self::SliceKind::*;
6869

6970
use super::usefulness::{MatchCheckCtxt, PatCtxt};
@@ -93,20 +94,99 @@ enum Presence {
9394
Seen,
9495
}
9596

97+
/// A possibly infinite integer. Values are encoded such that the ordering on `u128` matches the
98+
/// natural order on the original type. For example, `-128i8` is encoded as `0` and `127i8` as
99+
/// `255`. See `signed_bias` for details.
100+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
101+
enum MaybeInfiniteInt {
102+
NegInfinity,
103+
/// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite`.
104+
Finite(u128),
105+
/// The integer after `u128::MAX`. Used when we switch to exclusive ranges in `IntRange::split`.
106+
JustAfterMax,
107+
PosInfinity,
108+
}
109+
110+
impl MaybeInfiniteInt {
111+
// The return value of `signed_bias` should be XORed with a value to encode/decode it.
112+
fn signed_bias(tcx: TyCtxt<'_>, ty: Ty<'_>) -> u128 {
113+
match *ty.kind() {
114+
ty::Int(ity) => {
115+
let bits = Integer::from_int_ty(&tcx, ity).size().bits() as u128;
116+
1u128 << (bits - 1)
117+
}
118+
_ => 0,
119+
}
120+
}
121+
122+
fn new_finite(tcx: TyCtxt<'_>, ty: Ty<'_>, bits: u128) -> Self {
123+
let bias = Self::signed_bias(tcx, ty);
124+
// Perform a shift if the underlying types are signed, which makes the interval arithmetic
125+
// type-independent.
126+
let x = bits ^ bias;
127+
Finite(x)
128+
}
129+
fn from_pat_range_bdy<'tcx>(
130+
bdy: PatRangeBoundary<'tcx>,
131+
ty: Ty<'tcx>,
132+
tcx: TyCtxt<'tcx>,
133+
param_env: ty::ParamEnv<'tcx>,
134+
) -> Self {
135+
match bdy {
136+
PatRangeBoundary::NegInfinity => NegInfinity,
137+
PatRangeBoundary::Finite(value) => {
138+
let bits = value.eval_bits(tcx, param_env);
139+
Self::new_finite(tcx, ty, bits)
140+
}
141+
PatRangeBoundary::PosInfinity => PosInfinity,
142+
}
143+
}
144+
fn to_pat_range_bdy<'tcx>(self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> PatRangeBoundary<'tcx> {
145+
match self {
146+
NegInfinity => PatRangeBoundary::NegInfinity,
147+
Finite(x) => {
148+
let bias = Self::signed_bias(tcx, ty);
149+
let bits = x ^ bias;
150+
let env = ty::ParamEnv::empty().and(ty);
151+
let value = mir::Const::from_bits(tcx, bits, env);
152+
PatRangeBoundary::Finite(value)
153+
}
154+
JustAfterMax | PosInfinity => PatRangeBoundary::PosInfinity,
155+
}
156+
}
157+
158+
fn minus_one(self) -> Self {
159+
match self {
160+
Finite(n) => match n.checked_sub(1) {
161+
Some(m) => Finite(m),
162+
None => NegInfinity,
163+
},
164+
JustAfterMax => Finite(u128::MAX),
165+
x => x,
166+
}
167+
}
168+
fn plus_one(self) -> Self {
169+
match self {
170+
Finite(n) => match n.checked_add(1) {
171+
Some(m) => Finite(m),
172+
None => JustAfterMax,
173+
},
174+
x => x,
175+
}
176+
}
177+
}
178+
96179
/// An inclusive interval, used for precise integer exhaustiveness checking.
97-
/// `IntRange`s always store a contiguous range. This means that values are
98-
/// encoded such that `0` encodes the minimum value for the integer,
99-
/// regardless of the signedness.
100-
/// For example, the pattern `-128..=127i8` is encoded as `0..=255`.
101-
/// This makes comparisons and arithmetic on interval endpoints much more
102-
/// straightforward. See `signed_bias` for details.
180+
/// `IntRange`s always store a contiguous range.
103181
///
104182
/// `IntRange` is never used to encode an empty range or a "range" that wraps
105183
/// around the (offset) space: i.e., `range.lo <= range.hi`.
184+
///
185+
/// The range can have open ends.
106186
#[derive(Clone, Copy, PartialEq, Eq)]
107187
pub(crate) struct IntRange {
108-
lo: u128,
109-
hi: u128,
188+
lo: MaybeInfiniteInt, // Must not be `PosInfinity`.
189+
hi: MaybeInfiniteInt, // Must not be `NegInfinity`.
110190
}
111191

112192
impl IntRange {
@@ -115,51 +195,29 @@ impl IntRange {
115195
matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_))
116196
}
117197

198+
/// Best effort; will not know that e.g. `255u8..` is a singleton.
118199
fn is_singleton(&self) -> bool {
119200
self.lo == self.hi
120201
}
121202

122203
#[inline]
123204
fn from_bits<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, bits: u128) -> IntRange {
124-
let bias = IntRange::signed_bias(tcx, ty);
125-
// Perform a shift if the underlying types are signed, which makes the interval arithmetic
126-
// type-independent.
127-
let val = bits ^ bias;
128-
IntRange { lo: val, hi: val }
205+
let x = MaybeInfiniteInt::new_finite(tcx, ty, bits);
206+
IntRange { lo: x, hi: x }
129207
}
130208

131209
#[inline]
132-
fn from_range<'tcx>(
133-
tcx: TyCtxt<'tcx>,
134-
lo: u128,
135-
hi: u128,
136-
ty: Ty<'tcx>,
137-
end: RangeEnd,
138-
) -> IntRange {
139-
// Perform a shift if the underlying types are signed, which makes the interval arithmetic
140-
// type-independent.
141-
let bias = IntRange::signed_bias(tcx, ty);
142-
let (lo, hi) = (lo ^ bias, hi ^ bias);
143-
let offset = (end == RangeEnd::Excluded) as u128;
144-
let hi = hi - offset;
210+
fn from_range(lo: MaybeInfiniteInt, mut hi: MaybeInfiniteInt, end: RangeEnd) -> IntRange {
211+
if end == RangeEnd::Excluded {
212+
hi = hi.minus_one();
213+
}
145214
if lo > hi {
146215
// This should have been caught earlier by E0030.
147-
bug!("malformed range pattern: {lo}..={hi}");
216+
bug!("malformed range pattern: {lo:?}..={hi:?}");
148217
}
149218
IntRange { lo, hi }
150219
}
151220

152-
// The return value of `signed_bias` should be XORed with an endpoint to encode/decode it.
153-
fn signed_bias(tcx: TyCtxt<'_>, ty: Ty<'_>) -> u128 {
154-
match *ty.kind() {
155-
ty::Int(ity) => {
156-
let bits = Integer::from_int_ty(&tcx, ity).size().bits() as u128;
157-
1u128 << (bits - 1)
158-
}
159-
_ => 0,
160-
}
161-
}
162-
163221
fn is_subrange(&self, other: &Self) -> bool {
164222
other.lo <= self.lo && self.hi <= other.hi
165223
}
@@ -220,29 +278,16 @@ impl IntRange {
220278
&self,
221279
column_ranges: impl Iterator<Item = IntRange>,
222280
) -> impl Iterator<Item = (Presence, IntRange)> {
223-
/// Represents a boundary between 2 integers. Because the intervals spanning boundaries must be
224-
/// able to cover every integer, we need to be able to represent 2^128 + 1 such boundaries.
225-
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
226-
enum IntBoundary {
227-
JustBefore(u128),
228-
AfterMax,
229-
}
230-
231-
fn unpack_intrange(range: IntRange) -> [IntBoundary; 2] {
232-
use IntBoundary::*;
233-
let lo = JustBefore(range.lo);
234-
let hi = match range.hi.checked_add(1) {
235-
Some(m) => JustBefore(m),
236-
None => AfterMax,
237-
};
238-
[lo, hi]
281+
// Make the range into an exclusive range.
282+
fn unpack_intrange(range: IntRange) -> [MaybeInfiniteInt; 2] {
283+
[range.lo, range.hi.plus_one()]
239284
}
240285

241286
// The boundaries of ranges in `column_ranges` intersected with `self`.
242287
// We do parenthesis matching for input ranges. A boundary counts as +1 if it starts
243288
// a range and -1 if it ends it. When the count is > 0 between two boundaries, we
244289
// are within an input range.
245-
let mut boundaries: Vec<(IntBoundary, isize)> = column_ranges
290+
let mut boundaries: Vec<(MaybeInfiniteInt, isize)> = column_ranges
246291
.filter_map(|r| self.intersection(&r))
247292
.map(unpack_intrange)
248293
.flat_map(|[lo, hi]| [(lo, 1), (hi, -1)])
@@ -252,7 +297,7 @@ impl IntRange {
252297
// the accumulated count between distinct boundary values.
253298
boundaries.sort_unstable();
254299

255-
let [self_start, self_end] = unpack_intrange(self.clone());
300+
let [self_start, self_end] = unpack_intrange(*self);
256301
// Accumulate parenthesis counts.
257302
let mut paren_counter = 0isize;
258303
// Gather pairs of adjacent boundaries.
@@ -274,36 +319,26 @@ impl IntRange {
274319
.filter(|&(prev_bdy, _, bdy)| prev_bdy != bdy)
275320
// Convert back to ranges.
276321
.map(move |(prev_bdy, paren_count, bdy)| {
277-
use IntBoundary::*;
278322
use Presence::*;
279323
let presence = if paren_count > 0 { Seen } else { Unseen };
280-
let (lo, hi) = match (prev_bdy, bdy) {
281-
(JustBefore(n), JustBefore(m)) if n < m => (n, m - 1),
282-
(JustBefore(n), AfterMax) => (n, u128::MAX),
283-
_ => unreachable!(), // Ruled out by the sorting and filtering we did
284-
};
285-
(presence, IntRange { lo, hi })
324+
// Turn back into an inclusive range.
325+
let range = IntRange::from_range(prev_bdy, bdy, RangeEnd::Excluded);
326+
(presence, range)
286327
})
287328
}
288329

289330
/// Only used for displaying the range.
290-
fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> {
291-
let bias = IntRange::signed_bias(tcx, ty);
292-
let (lo_bits, hi_bits) = (self.lo ^ bias, self.hi ^ bias);
293-
294-
let env = ty::ParamEnv::empty().and(ty);
295-
let lo_const = mir::Const::from_bits(tcx, lo_bits, env);
296-
let hi_const = mir::Const::from_bits(tcx, hi_bits, env);
297-
298-
let kind = if lo_bits == hi_bits {
299-
PatKind::Constant { value: lo_const }
331+
fn to_pat<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Pat<'tcx> {
332+
let lo = self.lo.to_pat_range_bdy(ty, tcx);
333+
let hi = self.hi.to_pat_range_bdy(ty, tcx);
334+
335+
let kind = if self.is_singleton() {
336+
let value = lo.as_finite().unwrap();
337+
PatKind::Constant { value }
338+
} else if matches!((self.lo, self.hi), (NegInfinity, PosInfinity)) {
339+
PatKind::Wild
300340
} else {
301-
PatKind::Range(Box::new(PatRange {
302-
lo: PatRangeBoundary::Finite(lo_const),
303-
hi: PatRangeBoundary::Finite(hi_const),
304-
end: RangeEnd::Included,
305-
ty,
306-
}))
341+
PatKind::Range(Box::new(PatRange { lo, hi, end: RangeEnd::Included, ty }))
307342
};
308343

309344
Pat { ty, span: DUMMY_SP, kind }
@@ -339,7 +374,7 @@ impl IntRange {
339374
.filter_map(|pat| Some((pat.ctor().as_int_range()?, pat.span())))
340375
.filter(|(range, _)| self.suspicious_intersection(range))
341376
.map(|(range, span)| Overlap {
342-
range: self.intersection(&range).unwrap().to_pat(pcx.cx.tcx, pcx.ty),
377+
range: self.intersection(&range).unwrap().to_pat(pcx.ty, pcx.cx.tcx),
343378
span,
344379
})
345380
.collect();
@@ -359,10 +394,14 @@ impl IntRange {
359394
/// first.
360395
impl fmt::Debug for IntRange {
361396
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362-
let (lo, hi) = (self.lo, self.hi);
363-
write!(f, "{lo}")?;
397+
if let Finite(lo) = self.lo {
398+
write!(f, "{lo}")?;
399+
}
364400
write!(f, "{}", RangeEnd::Included)?;
365-
write!(f, "{hi}")
401+
if let Finite(hi) = self.hi {
402+
write!(f, "{hi}")?;
403+
}
404+
Ok(())
366405
}
367406
}
368407

@@ -919,8 +958,13 @@ struct SplitConstructorSet<'tcx> {
919958
impl ConstructorSet {
920959
#[instrument(level = "debug", skip(cx), ret)]
921960
pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> Self {
922-
let make_range =
923-
|start, end| IntRange::from_range(cx.tcx, start, end, ty, RangeEnd::Included);
961+
let make_range = |start, end| {
962+
IntRange::from_range(
963+
MaybeInfiniteInt::new_finite(cx.tcx, ty, start),
964+
MaybeInfiniteInt::new_finite(cx.tcx, ty, end),
965+
RangeEnd::Included,
966+
)
967+
};
924968
// This determines the set of all possible constructors for the type `ty`. For numbers,
925969
// arrays and slices we use ranges and variable-length slices when appropriate.
926970
//
@@ -1504,24 +1548,33 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
15041548
}
15051549
}
15061550
PatKind::Range(box PatRange { lo, hi, end, .. }) => {
1507-
use rustc_apfloat::Float;
15081551
let ty = pat.ty;
1509-
// FIXME: handle half-open ranges
1510-
let lo = lo.eval_bits(ty, cx.tcx, cx.param_env);
1511-
let hi = hi.eval_bits(ty, cx.tcx, cx.param_env);
15121552
ctor = match ty.kind() {
15131553
ty::Char | ty::Int(_) | ty::Uint(_) => {
1514-
IntRange(IntRange::from_range(cx.tcx, lo, hi, ty, *end))
1515-
}
1516-
ty::Float(ty::FloatTy::F32) => {
1517-
let lo = rustc_apfloat::ieee::Single::from_bits(lo);
1518-
let hi = rustc_apfloat::ieee::Single::from_bits(hi);
1519-
F32Range(lo, hi, *end)
1554+
let lo =
1555+
MaybeInfiniteInt::from_pat_range_bdy(*lo, ty, cx.tcx, cx.param_env);
1556+
let hi =
1557+
MaybeInfiniteInt::from_pat_range_bdy(*hi, ty, cx.tcx, cx.param_env);
1558+
IntRange(IntRange::from_range(lo, hi, *end))
15201559
}
1521-
ty::Float(ty::FloatTy::F64) => {
1522-
let lo = rustc_apfloat::ieee::Double::from_bits(lo);
1523-
let hi = rustc_apfloat::ieee::Double::from_bits(hi);
1524-
F64Range(lo, hi, *end)
1560+
ty::Float(fty) => {
1561+
use rustc_apfloat::Float;
1562+
let lo = lo.as_finite().map(|c| c.eval_bits(cx.tcx, cx.param_env));
1563+
let hi = hi.as_finite().map(|c| c.eval_bits(cx.tcx, cx.param_env));
1564+
match fty {
1565+
ty::FloatTy::F32 => {
1566+
use rustc_apfloat::ieee::Single;
1567+
let lo = lo.map(Single::from_bits).unwrap_or(-Single::INFINITY);
1568+
let hi = hi.map(Single::from_bits).unwrap_or(Single::INFINITY);
1569+
F32Range(lo, hi, *end)
1570+
}
1571+
ty::FloatTy::F64 => {
1572+
use rustc_apfloat::ieee::Double;
1573+
let lo = lo.map(Double::from_bits).unwrap_or(-Double::INFINITY);
1574+
let hi = hi.map(Double::from_bits).unwrap_or(Double::INFINITY);
1575+
F64Range(lo, hi, *end)
1576+
}
1577+
}
15251578
}
15261579
_ => bug!("invalid type for range pattern: {}", ty),
15271580
};
@@ -1628,7 +1681,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
16281681
}
16291682
}
16301683
Bool(b) => PatKind::Constant { value: mir::Const::from_bool(cx.tcx, *b) },
1631-
IntRange(range) => return range.to_pat(cx.tcx, self.ty),
1684+
IntRange(range) => return range.to_pat(self.ty, cx.tcx),
16321685
&Str(value) => PatKind::Constant { value },
16331686
Wildcard | NonExhaustive | Hidden => PatKind::Wild,
16341687
Missing { .. } => bug!(

0 commit comments

Comments
 (0)