Skip to content

Commit a4875ae

Browse files
committed
Match usize/isize exhaustively
1 parent 6f35ae6 commit a4875ae

18 files changed

+321
-307
lines changed

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

+21-6
Original file line numberDiff line numberDiff line change
@@ -703,14 +703,21 @@ fn report_arm_reachability<'p, 'tcx>(
703703
}
704704

705705
fn collect_non_exhaustive_tys<'tcx>(
706+
tcx: TyCtxt<'tcx>,
706707
pat: &WitnessPat<'tcx>,
707708
non_exhaustive_tys: &mut FxHashSet<Ty<'tcx>>,
708709
) {
709710
if matches!(pat.ctor(), Constructor::NonExhaustive) {
710711
non_exhaustive_tys.insert(pat.ty());
711712
}
713+
if let Constructor::IntRange(range) = pat.ctor() {
714+
if range.is_beyond_boundaries(pat.ty(), tcx) {
715+
// The range denotes the values before `isize::MIN` or the values after `usize::MAX`/`isize::MAX`.
716+
non_exhaustive_tys.insert(pat.ty());
717+
}
718+
}
712719
pat.iter_fields()
713-
.for_each(|field_pat| collect_non_exhaustive_tys(field_pat, non_exhaustive_tys))
720+
.for_each(|field_pat| collect_non_exhaustive_tys(tcx, field_pat, non_exhaustive_tys))
714721
}
715722

716723
/// Report that a match is not exhaustive.
@@ -764,16 +771,24 @@ fn non_exhaustive_match<'p, 'tcx>(
764771
adt_defined_here(cx, &mut err, scrut_ty, &witnesses);
765772
err.note(format!("the matched value is of type `{}`", scrut_ty));
766773

767-
if !is_empty_match && witnesses.len() == 1 {
774+
if !is_empty_match {
768775
let mut non_exhaustive_tys = FxHashSet::default();
769-
collect_non_exhaustive_tys(&witnesses[0], &mut non_exhaustive_tys);
776+
// Look at the first witness.
777+
collect_non_exhaustive_tys(cx.tcx, &witnesses[0], &mut non_exhaustive_tys);
770778

771779
for ty in non_exhaustive_tys {
772780
if ty.is_ptr_sized_integral() {
773-
err.note(format!(
774-
"`{ty}` does not have a fixed maximum value, so a wildcard `_` is necessary to match \
775-
exhaustively",
781+
if ty == cx.tcx.types.usize {
782+
err.note(format!(
783+
"`{ty}` does not have a fixed maximum value, so half-open ranges are necessary to match \
784+
exhaustively",
776785
));
786+
} else if ty == cx.tcx.types.isize {
787+
err.note(format!(
788+
"`{ty}` does not have fixed minimum and maximum values, so half-open ranges are necessary to match \
789+
exhaustively",
790+
));
791+
}
777792
if cx.tcx.sess.is_nightly_build() {
778793
err.help(format!(
779794
"add `#![feature(precise_pointer_size_matching)]` to the crate attributes to \

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

+92-44
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ use rustc_hir::RangeEnd;
5656
use rustc_index::Idx;
5757
use rustc_middle::middle::stability::EvalResult;
5858
use rustc_middle::mir;
59+
use rustc_middle::mir::interpret::Scalar;
5960
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange, PatRangeBoundary};
6061
use rustc_middle::ty::layout::IntegerExt;
6162
use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
@@ -139,20 +140,32 @@ impl MaybeInfiniteInt {
139140
PatRangeBoundary::PosInfinity => PosInfinity,
140141
}
141142
}
143+
// This could change from finite to infinite if we got `usize::MAX+1` after range splitting.
142144
fn to_pat_range_bdy<'tcx>(self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> PatRangeBoundary<'tcx> {
143145
match self {
144146
NegInfinity => PatRangeBoundary::NegInfinity,
145147
Finite(x) => {
146148
let bias = Self::signed_bias(tcx, ty);
147149
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)
150+
let size = ty.primitive_size(tcx);
151+
match Scalar::try_from_uint(bits, size) {
152+
Some(scalar) => {
153+
let value = mir::Const::from_scalar(tcx, scalar, ty);
154+
PatRangeBoundary::Finite(value)
155+
}
156+
// The value doesn't fit. Since `x >= 0` and 0 always encodes the minimum value
157+
// for a type, the problem isn't that the value is too small. So it must be too
158+
// large.
159+
None => PatRangeBoundary::PosInfinity,
160+
}
151161
}
152162
JustAfterMax | PosInfinity => PatRangeBoundary::PosInfinity,
153163
}
154164
}
155165

166+
fn is_finite(self) -> bool {
167+
matches!(self, Finite(_))
168+
}
156169
fn minus_one(self) -> Self {
157170
match self {
158171
Finite(n) => match n.checked_sub(1) {
@@ -169,22 +182,24 @@ impl MaybeInfiniteInt {
169182
Some(m) => Finite(m),
170183
None => JustAfterMax,
171184
},
185+
JustAfterMax => bug!(),
172186
x => x,
173187
}
174188
}
175189
}
176190

177-
/// An inclusive interval, used for precise integer exhaustiveness checking.
178-
/// `IntRange`s always store a contiguous range.
191+
/// An inclusive interval, used for precise integer exhaustiveness checking. `IntRange`s always
192+
/// store a contiguous range.
179193
///
180-
/// `IntRange` is never used to encode an empty range or a "range" that wraps
181-
/// around the (offset) space: i.e., `range.lo <= range.hi`.
194+
/// `IntRange` is never used to encode an empty range or a "range" that wraps around the (offset)
195+
/// space: i.e., `range.lo <= range.hi`.
182196
///
183-
/// The range can have open ends.
197+
/// Note: the range can be `NegInfinity..=NegInfinity` or `PosInfinity..=PosInfinity` to represent
198+
/// the values before `isize::MIN` and after `isize::MAX`/`usize::MAX`.
184199
#[derive(Clone, Copy, PartialEq, Eq)]
185200
pub(crate) struct IntRange {
186-
pub(crate) lo: MaybeInfiniteInt, // Must not be `PosInfinity`.
187-
pub(crate) hi: MaybeInfiniteInt, // Must not be `NegInfinity`.
201+
pub(crate) lo: MaybeInfiniteInt,
202+
pub(crate) hi: MaybeInfiniteInt,
188203
}
189204

190205
impl IntRange {
@@ -195,9 +210,7 @@ impl IntRange {
195210

196211
/// Best effort; will not know that e.g. `255u8..` is a singleton.
197212
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.
200-
self.lo == self.hi
213+
self.lo == self.hi && self.lo.is_finite()
201214
}
202215

203216
#[inline]
@@ -310,18 +323,49 @@ impl IntRange {
310323
})
311324
}
312325

326+
/// Whether the range denotes the values before `isize::MIN` or the values after
327+
/// `usize::MAX`/`isize::MAX`.
328+
pub(crate) fn is_beyond_boundaries<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool {
329+
// First check if we are usize/isize to avoid unnecessary `to_pat_range_bdy`.
330+
ty.is_ptr_sized_integral() && !tcx.features().precise_pointer_size_matching && {
331+
let lo = self.lo.to_pat_range_bdy(ty, tcx);
332+
let hi = self.hi.to_pat_range_bdy(ty, tcx);
333+
matches!(lo, PatRangeBoundary::PosInfinity)
334+
|| matches!(hi, PatRangeBoundary::NegInfinity)
335+
}
336+
}
313337
/// Only used for displaying the range.
314338
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() {
339+
let kind = if matches!((self.lo, self.hi), (NegInfinity, PosInfinity)) {
340+
PatKind::Wild
341+
} else if self.is_singleton() {
342+
let lo = self.lo.to_pat_range_bdy(ty, tcx);
319343
let value = lo.as_finite().unwrap();
320344
PatKind::Constant { value }
321-
} else if matches!((self.lo, self.hi), (NegInfinity, PosInfinity)) {
322-
PatKind::Wild
323345
} else {
324-
PatKind::Range(Box::new(PatRange { lo, hi, end: RangeEnd::Included, ty }))
346+
let mut lo = self.lo.to_pat_range_bdy(ty, tcx);
347+
let mut hi = self.hi.to_pat_range_bdy(ty, tcx);
348+
let end = if hi.is_finite() {
349+
RangeEnd::Included
350+
} else {
351+
// `0..=` isn't a valid pattern.
352+
RangeEnd::Excluded
353+
};
354+
if matches!(hi, PatRangeBoundary::NegInfinity) {
355+
// The range denotes the values before `isize::MIN`.
356+
let c = ty.numeric_min_val(tcx).unwrap();
357+
let value = mir::Const::from_ty_const(c, tcx);
358+
hi = PatRangeBoundary::Finite(value);
359+
}
360+
if matches!(lo, PatRangeBoundary::PosInfinity) {
361+
// The range denotes the values after `usize::MAX`/`isize::MAX`.
362+
// We represent this as `usize::MAX..` which is slightly incorrect but probably
363+
// clear enough.
364+
let c = ty.numeric_max_val(tcx).unwrap();
365+
let value = mir::Const::from_ty_const(c, tcx);
366+
lo = PatRangeBoundary::Finite(value);
367+
}
368+
PatKind::Range(Box::new(PatRange { lo, hi, end, ty }))
325369
};
326370

327371
Pat { ty, span: DUMMY_SP, kind }
@@ -843,9 +887,7 @@ pub(super) enum ConstructorSet {
843887
Bool,
844888
/// The type is spanned by integer values. The range or ranges give the set of allowed values.
845889
/// The second range is only useful for `char`.
846-
/// `non_exhaustive` is used when the range is not allowed to be matched exhaustively (that's
847-
/// for usize/isize).
848-
Integers { range_1: IntRange, range_2: Option<IntRange>, non_exhaustive: bool },
890+
Integers { range_1: IntRange, range_2: Option<IntRange> },
849891
/// The type is matched by slices. The usize is the compile-time length of the array, if known.
850892
Slice(Option<usize>),
851893
/// The type is matched by slices whose elements are uninhabited.
@@ -903,27 +945,37 @@ impl ConstructorSet {
903945
Self::Integers {
904946
range_1: make_range('\u{0000}' as u128, '\u{D7FF}' as u128),
905947
range_2: Some(make_range('\u{E000}' as u128, '\u{10FFFF}' as u128)),
906-
non_exhaustive: false,
907948
}
908949
}
909950
&ty::Int(ity) => {
910-
// `usize`/`isize` are not allowed to be matched exhaustively unless the
911-
// `precise_pointer_size_matching` feature is enabled.
912-
let non_exhaustive =
913-
ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching;
914-
let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128;
915-
let min = 1u128 << (bits - 1);
916-
let max = min - 1;
917-
Self::Integers { range_1: make_range(min, max), non_exhaustive, range_2: None }
951+
let range = if ty.is_ptr_sized_integral()
952+
&& !cx.tcx.features().precise_pointer_size_matching
953+
{
954+
// The min/max values of `isize` are not allowed to be observed unless the
955+
// `precise_pointer_size_matching` feature is enabled.
956+
IntRange { lo: NegInfinity, hi: PosInfinity }
957+
} else {
958+
let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128;
959+
let min = 1u128 << (bits - 1);
960+
let max = min - 1;
961+
make_range(min, max)
962+
};
963+
Self::Integers { range_1: range, range_2: None }
918964
}
919965
&ty::Uint(uty) => {
920-
// `usize`/`isize` are not allowed to be matched exhaustively unless the
921-
// `precise_pointer_size_matching` feature is enabled.
922-
let non_exhaustive =
923-
ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching;
924-
let size = Integer::from_uint_ty(&cx.tcx, uty).size();
925-
let max = size.truncate(u128::MAX);
926-
Self::Integers { range_1: make_range(0, max), non_exhaustive, range_2: None }
966+
let range = if ty.is_ptr_sized_integral()
967+
&& !cx.tcx.features().precise_pointer_size_matching
968+
{
969+
// The max value of `usize` is not allowed to be observed unless the
970+
// `precise_pointer_size_matching` feature is enabled.
971+
let lo = MaybeInfiniteInt::new_finite(cx.tcx, ty, 0);
972+
IntRange { lo, hi: PosInfinity }
973+
} else {
974+
let size = Integer::from_uint_ty(&cx.tcx, uty).size();
975+
let max = size.truncate(u128::MAX);
976+
make_range(0, max)
977+
};
978+
Self::Integers { range_1: range, range_2: None }
927979
}
928980
ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => {
929981
let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize;
@@ -1078,7 +1130,7 @@ impl ConstructorSet {
10781130
missing.push(Bool(true));
10791131
}
10801132
}
1081-
ConstructorSet::Integers { range_1, range_2, non_exhaustive } => {
1133+
ConstructorSet::Integers { range_1, range_2 } => {
10821134
let seen_ranges: Vec<_> =
10831135
seen.map(|ctor| ctor.as_int_range().unwrap().clone()).collect();
10841136
for (seen, splitted_range) in range_1.split(seen_ranges.iter().cloned()) {
@@ -1095,10 +1147,6 @@ impl ConstructorSet {
10951147
}
10961148
}
10971149
}
1098-
1099-
if *non_exhaustive {
1100-
missing.push(NonExhaustive);
1101-
}
11021150
}
11031151
&ConstructorSet::Slice(array_len) => {
11041152
let seen_slices = seen.map(|c| c.as_slice().unwrap());
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
fn main() {
22
match 0usize {
3-
//~^ ERROR non-exhaustive patterns: `_` not covered
4-
//~| NOTE pattern `_` not covered
3+
//~^ ERROR non-exhaustive patterns: `usize::MAX..` not covered
4+
//~| NOTE pattern `usize::MAX..` not covered
55
//~| NOTE the matched value is of type `usize`
66
//~| NOTE `usize` does not have a fixed maximum value
77
0..=usize::MAX => {}
88
}
99

1010
match 0isize {
11-
//~^ ERROR non-exhaustive patterns: `_` not covered
12-
//~| NOTE pattern `_` not covered
11+
//~^ ERROR non-exhaustive patterns: `..isize::MIN` and `isize::MAX..` not covered
12+
//~| NOTE patterns `..isize::MIN` and `isize::MAX..` not covered
1313
//~| NOTE the matched value is of type `isize`
14-
//~| NOTE `isize` does not have a fixed maximum value
14+
//~| NOTE `isize` does not have fixed minimum and maximum values
1515
isize::MIN..=isize::MAX => {}
1616
}
1717
}

Diff for: tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.stderr

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
1-
error[E0004]: non-exhaustive patterns: `_` not covered
1+
error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered
22
--> $DIR/feature-gate-precise_pointer_size_matching.rs:2:11
33
|
44
LL | match 0usize {
5-
| ^^^^^^ pattern `_` not covered
5+
| ^^^^^^ pattern `usize::MAX..` not covered
66
|
77
= note: the matched value is of type `usize`
8-
= note: `usize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively
8+
= note: `usize` does not have a fixed maximum value, so half-open ranges are necessary to match exhaustively
99
= help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching
1010
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
1111
|
1212
LL ~ 0..=usize::MAX => {},
13-
LL + _ => todo!()
13+
LL + usize::MAX.. => todo!()
1414
|
1515

16-
error[E0004]: non-exhaustive patterns: `_` not covered
16+
error[E0004]: non-exhaustive patterns: `..isize::MIN` and `isize::MAX..` not covered
1717
--> $DIR/feature-gate-precise_pointer_size_matching.rs:10:11
1818
|
1919
LL | match 0isize {
20-
| ^^^^^^ pattern `_` not covered
20+
| ^^^^^^ patterns `..isize::MIN` and `isize::MAX..` not covered
2121
|
2222
= note: the matched value is of type `isize`
23-
= note: `isize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively
23+
= note: `isize` does not have fixed minimum and maximum values, so half-open ranges are necessary to match exhaustively
2424
= help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `isize` matching
25-
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
25+
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
2626
|
2727
LL ~ isize::MIN..=isize::MAX => {},
28-
LL + _ => todo!()
28+
LL + ..isize::MIN | isize::MAX.. => todo!()
2929
|
3030

3131
error: aborting due to 2 previous errors

Diff for: tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0004]: non-exhaustive patterns: type `usize` is non-empty
2-
--> $DIR/pointer-sized-int.rs:58:11
2+
--> $DIR/pointer-sized-int.rs:54:11
33
|
44
LL | match 7usize {}
55
| ^^^^^^

0 commit comments

Comments
 (0)