Skip to content

Commit 6f35ae6

Browse files
committed
Propagate half-open ranges through exhaustiveness checking
1 parent 9bc4c37 commit 6f35ae6

File tree

2 files changed

+158
-102
lines changed

2 files changed

+158
-102
lines changed

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

Lines changed: 154 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ use rustc_span::{Span, DUMMY_SP};
6363
use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};
6464

6565
use self::Constructor::*;
66+
use self::MaybeInfiniteInt::*;
6667
use self::SliceKind::*;
6768

6869
use super::usefulness::{MatchCheckCtxt, PatCtxt};
@@ -91,20 +92,99 @@ enum Presence {
9192
Seen,
9293
}
9394

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

110190
impl IntRange {
@@ -113,51 +193,31 @@ impl IntRange {
113193
matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_))
114194
}
115195

196+
/// Best effort; will not know that e.g. `255u8..` is a singleton.
116197
pub(super) fn is_singleton(&self) -> bool {
198+
// Since `lo` and `hi` can't be the same `Infinity`, this correctly only detects a
199+
// `Finite(x)` singleton.
117200
self.lo == self.hi
118201
}
119202

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

129209
#[inline]
130-
fn from_range<'tcx>(
131-
tcx: TyCtxt<'tcx>,
132-
lo: u128,
133-
hi: u128,
134-
ty: Ty<'tcx>,
135-
end: RangeEnd,
136-
) -> IntRange {
137-
// Perform a shift if the underlying types are signed, which makes the interval arithmetic
138-
// type-independent.
139-
let bias = IntRange::signed_bias(tcx, ty);
140-
let (lo, hi) = (lo ^ bias, hi ^ bias);
141-
let offset = (end == RangeEnd::Excluded) as u128;
142-
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+
}
143214
if lo > hi {
144215
// This should have been caught earlier by E0030.
145-
bug!("malformed range pattern: {lo}..={hi}");
216+
bug!("malformed range pattern: {lo:?}..={hi:?}");
146217
}
147218
IntRange { lo, hi }
148219
}
149220

150-
// The return value of `signed_bias` should be XORed with an endpoint to encode/decode it.
151-
fn signed_bias(tcx: TyCtxt<'_>, ty: Ty<'_>) -> u128 {
152-
match *ty.kind() {
153-
ty::Int(ity) => {
154-
let bits = Integer::from_int_ty(&tcx, ity).size().bits() as u128;
155-
1u128 << (bits - 1)
156-
}
157-
_ => 0,
158-
}
159-
}
160-
161221
fn is_subrange(&self, other: &Self) -> bool {
162222
other.lo <= self.lo && self.hi <= other.hi
163223
}
@@ -201,29 +261,16 @@ impl IntRange {
201261
&self,
202262
column_ranges: impl Iterator<Item = IntRange>,
203263
) -> impl Iterator<Item = (Presence, IntRange)> {
204-
/// Represents a boundary between 2 integers. Because the intervals spanning boundaries must be
205-
/// able to cover every integer, we need to be able to represent 2^128 + 1 such boundaries.
206-
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
207-
enum IntBoundary {
208-
JustBefore(u128),
209-
AfterMax,
210-
}
211-
212-
fn unpack_intrange(range: IntRange) -> [IntBoundary; 2] {
213-
use IntBoundary::*;
214-
let lo = JustBefore(range.lo);
215-
let hi = match range.hi.checked_add(1) {
216-
Some(m) => JustBefore(m),
217-
None => AfterMax,
218-
};
219-
[lo, hi]
264+
// Make the range into an exclusive range.
265+
fn unpack_intrange(range: IntRange) -> [MaybeInfiniteInt; 2] {
266+
[range.lo, range.hi.plus_one()]
220267
}
221268

222269
// The boundaries of ranges in `column_ranges` intersected with `self`.
223270
// We do parenthesis matching for input ranges. A boundary counts as +1 if it starts
224271
// a range and -1 if it ends it. When the count is > 0 between two boundaries, we
225272
// are within an input range.
226-
let mut boundaries: Vec<(IntBoundary, isize)> = column_ranges
273+
let mut boundaries: Vec<(MaybeInfiniteInt, isize)> = column_ranges
227274
.filter_map(|r| self.intersection(&r))
228275
.map(unpack_intrange)
229276
.flat_map(|[lo, hi]| [(lo, 1), (hi, -1)])
@@ -233,7 +280,7 @@ impl IntRange {
233280
// the accumulated count between distinct boundary values.
234281
boundaries.sort_unstable();
235282

236-
let [self_start, self_end] = unpack_intrange(self.clone());
283+
let [self_start, self_end] = unpack_intrange(*self);
237284
// Accumulate parenthesis counts.
238285
let mut paren_counter = 0isize;
239286
// Gather pairs of adjacent boundaries.
@@ -255,36 +302,26 @@ impl IntRange {
255302
.filter(|&(prev_bdy, _, bdy)| prev_bdy != bdy)
256303
// Convert back to ranges.
257304
.map(move |(prev_bdy, paren_count, bdy)| {
258-
use IntBoundary::*;
259305
use Presence::*;
260306
let presence = if paren_count > 0 { Seen } else { Unseen };
261-
let (lo, hi) = match (prev_bdy, bdy) {
262-
(JustBefore(n), JustBefore(m)) if n < m => (n, m - 1),
263-
(JustBefore(n), AfterMax) => (n, u128::MAX),
264-
_ => unreachable!(), // Ruled out by the sorting and filtering we did
265-
};
266-
(presence, IntRange { lo, hi })
307+
// Turn back into an inclusive range.
308+
let range = IntRange::from_range(prev_bdy, bdy, RangeEnd::Excluded);
309+
(presence, range)
267310
})
268311
}
269312

270313
/// Only used for displaying the range.
271-
pub(super) fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> {
272-
let bias = IntRange::signed_bias(tcx, ty);
273-
let (lo_bits, hi_bits) = (self.lo ^ bias, self.hi ^ bias);
274-
275-
let env = ty::ParamEnv::empty().and(ty);
276-
let lo_const = mir::Const::from_bits(tcx, lo_bits, env);
277-
let hi_const = mir::Const::from_bits(tcx, hi_bits, env);
278-
279-
let kind = if lo_bits == hi_bits {
280-
PatKind::Constant { value: lo_const }
314+
pub(super) fn to_pat<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Pat<'tcx> {
315+
let lo = self.lo.to_pat_range_bdy(ty, tcx);
316+
let hi = self.hi.to_pat_range_bdy(ty, tcx);
317+
318+
let kind = if self.is_singleton() {
319+
let value = lo.as_finite().unwrap();
320+
PatKind::Constant { value }
321+
} else if matches!((self.lo, self.hi), (NegInfinity, PosInfinity)) {
322+
PatKind::Wild
281323
} else {
282-
PatKind::Range(Box::new(PatRange {
283-
lo: PatRangeBoundary::Finite(lo_const),
284-
hi: PatRangeBoundary::Finite(hi_const),
285-
end: RangeEnd::Included,
286-
ty,
287-
}))
324+
PatKind::Range(Box::new(PatRange { lo, hi, end: RangeEnd::Included, ty }))
288325
};
289326

290327
Pat { ty, span: DUMMY_SP, kind }
@@ -295,10 +332,14 @@ impl IntRange {
295332
/// first.
296333
impl fmt::Debug for IntRange {
297334
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298-
let (lo, hi) = (self.lo, self.hi);
299-
write!(f, "{lo}")?;
335+
if let Finite(lo) = self.lo {
336+
write!(f, "{lo}")?;
337+
}
300338
write!(f, "{}", RangeEnd::Included)?;
301-
write!(f, "{hi}")
339+
if let Finite(hi) = self.hi {
340+
write!(f, "{hi}")?;
341+
}
342+
Ok(())
302343
}
303344
}
304345

@@ -840,8 +881,13 @@ pub(super) struct SplitConstructorSet<'tcx> {
840881
impl ConstructorSet {
841882
#[instrument(level = "debug", skip(cx), ret)]
842883
pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> Self {
843-
let make_range =
844-
|start, end| IntRange::from_range(cx.tcx, start, end, ty, RangeEnd::Included);
884+
let make_range = |start, end| {
885+
IntRange::from_range(
886+
MaybeInfiniteInt::new_finite(cx.tcx, ty, start),
887+
MaybeInfiniteInt::new_finite(cx.tcx, ty, end),
888+
RangeEnd::Included,
889+
)
890+
};
845891
// This determines the set of all possible constructors for the type `ty`. For numbers,
846892
// arrays and slices we use ranges and variable-length slices when appropriate.
847893
//
@@ -1419,24 +1465,33 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
14191465
}
14201466
}
14211467
PatKind::Range(box PatRange { lo, hi, end, .. }) => {
1422-
use rustc_apfloat::Float;
14231468
let ty = pat.ty;
1424-
// FIXME: handle half-open ranges
1425-
let lo = lo.eval_bits(ty, cx.tcx, cx.param_env);
1426-
let hi = hi.eval_bits(ty, cx.tcx, cx.param_env);
14271469
ctor = match ty.kind() {
14281470
ty::Char | ty::Int(_) | ty::Uint(_) => {
1429-
IntRange(IntRange::from_range(cx.tcx, lo, hi, ty, *end))
1430-
}
1431-
ty::Float(ty::FloatTy::F32) => {
1432-
let lo = rustc_apfloat::ieee::Single::from_bits(lo);
1433-
let hi = rustc_apfloat::ieee::Single::from_bits(hi);
1434-
F32Range(lo, hi, *end)
1471+
let lo =
1472+
MaybeInfiniteInt::from_pat_range_bdy(*lo, ty, cx.tcx, cx.param_env);
1473+
let hi =
1474+
MaybeInfiniteInt::from_pat_range_bdy(*hi, ty, cx.tcx, cx.param_env);
1475+
IntRange(IntRange::from_range(lo, hi, *end))
14351476
}
1436-
ty::Float(ty::FloatTy::F64) => {
1437-
let lo = rustc_apfloat::ieee::Double::from_bits(lo);
1438-
let hi = rustc_apfloat::ieee::Double::from_bits(hi);
1439-
F64Range(lo, hi, *end)
1477+
ty::Float(fty) => {
1478+
use rustc_apfloat::Float;
1479+
let lo = lo.as_finite().map(|c| c.eval_bits(cx.tcx, cx.param_env));
1480+
let hi = hi.as_finite().map(|c| c.eval_bits(cx.tcx, cx.param_env));
1481+
match fty {
1482+
ty::FloatTy::F32 => {
1483+
use rustc_apfloat::ieee::Single;
1484+
let lo = lo.map(Single::from_bits).unwrap_or(-Single::INFINITY);
1485+
let hi = hi.map(Single::from_bits).unwrap_or(Single::INFINITY);
1486+
F32Range(lo, hi, *end)
1487+
}
1488+
ty::FloatTy::F64 => {
1489+
use rustc_apfloat::ieee::Double;
1490+
let lo = lo.map(Double::from_bits).unwrap_or(-Double::INFINITY);
1491+
let hi = hi.map(Double::from_bits).unwrap_or(Double::INFINITY);
1492+
F64Range(lo, hi, *end)
1493+
}
1494+
}
14401495
}
14411496
_ => bug!("invalid type for range pattern: {}", ty),
14421497
};
@@ -1706,7 +1761,7 @@ impl<'tcx> WitnessPat<'tcx> {
17061761
let mut subpatterns = self.iter_fields().map(|p| Box::new(p.to_pat(cx)));
17071762
let kind = match &self.ctor {
17081763
Bool(b) => PatKind::Constant { value: mir::Const::from_bool(cx.tcx, *b) },
1709-
IntRange(range) => return range.to_pat(cx.tcx, self.ty),
1764+
IntRange(range) => return range.to_pat(self.ty, cx.tcx),
17101765
Single | Variant(_) => match self.ty.kind() {
17111766
ty::Tuple(..) => PatKind::Leaf {
17121767
subpatterns: subpatterns

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@
308308
use self::ArmType::*;
309309
use self::Usefulness::*;
310310
use super::deconstruct_pat::{
311-
Constructor, ConstructorSet, DeconstructedPat, IntRange, SplitConstructorSet, WitnessPat,
311+
Constructor, ConstructorSet, DeconstructedPat, IntRange, MaybeInfiniteInt, SplitConstructorSet,
312+
WitnessPat,
312313
};
313314
use crate::errors::{NonExhaustiveOmittedPattern, Overlap, OverlappingRangeEndpoints, Uncovered};
314315

@@ -1013,7 +1014,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>(
10131014

10141015
if IntRange::is_integral(ty) {
10151016
let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| {
1016-
let overlap_as_pat = overlap.to_pat(cx.tcx, ty);
1017+
let overlap_as_pat = overlap.to_pat(ty, cx.tcx);
10171018
let overlaps: Vec<_> = overlapped_spans
10181019
.iter()
10191020
.copied()
@@ -1031,7 +1032,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>(
10311032
let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range());
10321033
for overlap_range in split_int_ranges.clone() {
10331034
if overlap_range.is_singleton() {
1034-
let overlap: u128 = overlap_range.lo;
1035+
let overlap: MaybeInfiniteInt = overlap_range.lo;
10351036
// Ranges that look like `lo..=overlap`.
10361037
let mut prefixes: SmallVec<[_; 1]> = Default::default();
10371038
// Ranges that look like `overlap..=hi`.

0 commit comments

Comments
 (0)