Skip to content

Commit

Permalink
Match usize/isize exhaustively
Browse files Browse the repository at this point in the history
  • Loading branch information
Nadrieril committed Oct 21, 2023
1 parent 291ee39 commit 57cc6aa
Show file tree
Hide file tree
Showing 18 changed files with 323 additions and 306 deletions.
27 changes: 21 additions & 6 deletions compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,14 +703,21 @@ fn report_arm_reachability<'p, 'tcx>(
}

fn collect_non_exhaustive_tys<'tcx>(
tcx: TyCtxt<'tcx>,
pat: &WitnessPat<'tcx>,
non_exhaustive_tys: &mut FxHashSet<Ty<'tcx>>,
) {
if matches!(pat.ctor(), Constructor::NonExhaustive) {
non_exhaustive_tys.insert(pat.ty());
}
if let Constructor::IntRange(range) = pat.ctor() {
if range.is_beyond_boundaries(pat.ty(), tcx) {
// The range denotes the values before `isize::MIN` or the values after `usize::MAX`/`isize::MAX`.
non_exhaustive_tys.insert(pat.ty());
}
}
pat.iter_fields()
.for_each(|field_pat| collect_non_exhaustive_tys(field_pat, non_exhaustive_tys))
.for_each(|field_pat| collect_non_exhaustive_tys(tcx, field_pat, non_exhaustive_tys))
}

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

if !is_empty_match && witnesses.len() == 1 {
if !is_empty_match {
let mut non_exhaustive_tys = FxHashSet::default();
collect_non_exhaustive_tys(&witnesses[0], &mut non_exhaustive_tys);
// Look at the first witness.
collect_non_exhaustive_tys(cx.tcx, &witnesses[0], &mut non_exhaustive_tys);

for ty in non_exhaustive_tys {
if ty.is_ptr_sized_integral() {
err.note(format!(
"`{ty}` does not have a fixed maximum value, so a wildcard `_` is necessary to match \
exhaustively",
if ty == cx.tcx.types.usize {
err.note(format!(
"`{ty}` does not have a fixed maximum value, so half-open ranges are necessary to match \
exhaustively",
));
} else if ty == cx.tcx.types.isize {
err.note(format!(
"`{ty}` does not have fixed minimum and maximum values, so half-open ranges are necessary to match \
exhaustively",
));
}
if cx.tcx.sess.is_nightly_build() {
err.help(format!(
"add `#![feature(precise_pointer_size_matching)]` to the crate attributes to \
Expand Down
137 changes: 94 additions & 43 deletions compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ use rustc_hir::{HirId, RangeEnd};
use rustc_index::Idx;
use rustc_middle::middle::stability::EvalResult;
use rustc_middle::mir;
use rustc_middle::mir::interpret::Scalar;
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange, PatRangeBoundary};
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
Expand Down Expand Up @@ -141,20 +142,32 @@ impl MaybeInfiniteInt {
PatRangeBoundary::PosInfinity => PosInfinity,
}
}
// This could change from finite to infinite if we got `usize::MAX+1` after range splitting.
fn to_pat_range_bdy<'tcx>(self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> PatRangeBoundary<'tcx> {
match self {
NegInfinity => PatRangeBoundary::NegInfinity,
Finite(x) => {
let bias = Self::signed_bias(tcx, ty);
let bits = x ^ bias;
let env = ty::ParamEnv::empty().and(ty);
let value = mir::Const::from_bits(tcx, bits, env);
PatRangeBoundary::Finite(value)
let size = ty.primitive_size(tcx);
match Scalar::try_from_uint(bits, size) {
Some(scalar) => {
let value = mir::Const::from_scalar(tcx, scalar, ty);
PatRangeBoundary::Finite(value)
}
// The value doesn't fit. Since `x >= 0` and 0 always encodes the minimum value
// for a type, the problem isn't that the value is too small. So it must be too
// large.
None => PatRangeBoundary::PosInfinity,
}
}
JustAfterMax | PosInfinity => PatRangeBoundary::PosInfinity,
}
}

fn is_finite(self) -> bool {
matches!(self, Finite(_))
}
fn minus_one(self) -> Self {
match self {
Finite(n) => match n.checked_sub(1) {
Expand All @@ -171,22 +184,24 @@ impl MaybeInfiniteInt {
Some(m) => Finite(m),
None => JustAfterMax,
},
JustAfterMax => bug!(),
x => x,
}
}
}

/// An inclusive interval, used for precise integer exhaustiveness checking.
/// `IntRange`s always store a contiguous range.
/// An inclusive interval, used for precise integer exhaustiveness checking. `IntRange`s always
/// store a contiguous range.
///
/// `IntRange` is never used to encode an empty range or a "range" that wraps
/// around the (offset) space: i.e., `range.lo <= range.hi`.
/// `IntRange` is never used to encode an empty range or a "range" that wraps around the (offset)
/// space: i.e., `range.lo <= range.hi`.
///
/// The range can have open ends.
/// Note: the range can be `NegInfinity..=NegInfinity` or `PosInfinity..=PosInfinity` to represent
/// the values before `isize::MIN` and after `isize::MAX`/`usize::MAX`.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) struct IntRange {
lo: MaybeInfiniteInt, // Must not be `PosInfinity`.
hi: MaybeInfiniteInt, // Must not be `NegInfinity`.
lo: MaybeInfiniteInt,
hi: MaybeInfiniteInt,
}

impl IntRange {
Expand All @@ -197,7 +212,7 @@ impl IntRange {

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

#[inline]
Expand Down Expand Up @@ -242,7 +257,8 @@ impl IntRange {
// `true` in the following cases:
// 1 ------- // 1 -------
// 2 -------- // 2 -------
(self.lo == other.hi || self.hi == other.lo)
((self.lo == other.hi && self.lo.is_finite())
|| (self.hi == other.lo && self.hi.is_finite()))
&& !self.is_singleton()
&& !other.is_singleton()
}
Expand Down Expand Up @@ -327,18 +343,49 @@ impl IntRange {
})
}

/// Whether the range denotes the values before `isize::MIN` or the values after
/// `usize::MAX`/`isize::MAX`.
pub(crate) fn is_beyond_boundaries<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool {
// First check if we are usize/isize to avoid unnecessary `to_pat_range_bdy`.
ty.is_ptr_sized_integral() && !tcx.features().precise_pointer_size_matching && {
let lo = self.lo.to_pat_range_bdy(ty, tcx);
let hi = self.hi.to_pat_range_bdy(ty, tcx);
matches!(lo, PatRangeBoundary::PosInfinity)
|| matches!(hi, PatRangeBoundary::NegInfinity)
}
}
/// Only used for displaying the range.
fn to_pat<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Pat<'tcx> {
let lo = self.lo.to_pat_range_bdy(ty, tcx);
let hi = self.hi.to_pat_range_bdy(ty, tcx);

let kind = if self.is_singleton() {
let kind = if matches!((self.lo, self.hi), (NegInfinity, PosInfinity)) {
PatKind::Wild
} else if self.is_singleton() {
let lo = self.lo.to_pat_range_bdy(ty, tcx);
let value = lo.as_finite().unwrap();
PatKind::Constant { value }
} else if matches!((self.lo, self.hi), (NegInfinity, PosInfinity)) {
PatKind::Wild
} else {
PatKind::Range(Box::new(PatRange { lo, hi, end: RangeEnd::Included, ty }))
let mut lo = self.lo.to_pat_range_bdy(ty, tcx);
let mut hi = self.hi.to_pat_range_bdy(ty, tcx);
let end = if hi.is_finite() {
RangeEnd::Included
} else {
// `0..=` isn't a valid pattern.
RangeEnd::Excluded
};
if matches!(hi, PatRangeBoundary::NegInfinity) {
// The range denotes the values before `isize::MIN`.
let c = ty.numeric_min_val(tcx).unwrap();
let value = mir::Const::from_ty_const(c, tcx);
hi = PatRangeBoundary::Finite(value);
}
if matches!(lo, PatRangeBoundary::PosInfinity) {
// The range denotes the values after `usize::MAX`/`isize::MAX`.
// We represent this as `usize::MAX..` which is slightly incorrect but probably
// clear enough.
let c = ty.numeric_max_val(tcx).unwrap();
let value = mir::Const::from_ty_const(c, tcx);
lo = PatRangeBoundary::Finite(value);
}
PatKind::Range(Box::new(PatRange { lo, hi, end, ty }))
};

Pat { ty, span: DUMMY_SP, kind }
Expand Down Expand Up @@ -905,9 +952,7 @@ pub(super) enum ConstructorSet {
Bool,
/// The type is spanned by integer values. The range or ranges give the set of allowed values.
/// The second range is only useful for `char`.
/// `non_exhaustive` is used when the range is not allowed to be matched exhaustively (that's
/// for usize/isize).
Integers { range_1: IntRange, range_2: Option<IntRange>, non_exhaustive: bool },
Integers { range_1: IntRange, range_2: Option<IntRange> },
/// The type is matched by slices. The usize is the compile-time length of the array, if known.
Slice(Option<usize>),
/// The type is matched by slices whose elements are uninhabited.
Expand Down Expand Up @@ -965,27 +1010,37 @@ impl ConstructorSet {
Self::Integers {
range_1: make_range('\u{0000}' as u128, '\u{D7FF}' as u128),
range_2: Some(make_range('\u{E000}' as u128, '\u{10FFFF}' as u128)),
non_exhaustive: false,
}
}
&ty::Int(ity) => {
// `usize`/`isize` are not allowed to be matched exhaustively unless the
// `precise_pointer_size_matching` feature is enabled.
let non_exhaustive =
ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching;
let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128;
let min = 1u128 << (bits - 1);
let max = min - 1;
Self::Integers { range_1: make_range(min, max), non_exhaustive, range_2: None }
let range = if ty.is_ptr_sized_integral()
&& !cx.tcx.features().precise_pointer_size_matching
{
// The min/max values of `isize` are not allowed to be observed unless the
// `precise_pointer_size_matching` feature is enabled.
IntRange { lo: NegInfinity, hi: PosInfinity }
} else {
let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128;
let min = 1u128 << (bits - 1);
let max = min - 1;
make_range(min, max)
};
Self::Integers { range_1: range, range_2: None }
}
&ty::Uint(uty) => {
// `usize`/`isize` are not allowed to be matched exhaustively unless the
// `precise_pointer_size_matching` feature is enabled.
let non_exhaustive =
ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching;
let size = Integer::from_uint_ty(&cx.tcx, uty).size();
let max = size.truncate(u128::MAX);
Self::Integers { range_1: make_range(0, max), non_exhaustive, range_2: None }
let range = if ty.is_ptr_sized_integral()
&& !cx.tcx.features().precise_pointer_size_matching
{
// The max value of `usize` is not allowed to be observed unless the
// `precise_pointer_size_matching` feature is enabled.
let lo = MaybeInfiniteInt::new_finite(cx.tcx, ty, 0);
IntRange { lo, hi: PosInfinity }
} else {
let size = Integer::from_uint_ty(&cx.tcx, uty).size();
let max = size.truncate(u128::MAX);
make_range(0, max)
};
Self::Integers { range_1: range, range_2: None }
}
ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => {
let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize;
Expand Down Expand Up @@ -1140,7 +1195,7 @@ impl ConstructorSet {
missing.push(Bool(true));
}
}
ConstructorSet::Integers { range_1, range_2, non_exhaustive } => {
ConstructorSet::Integers { range_1, range_2 } => {
let seen_ranges: Vec<_> =
seen.map(|ctor| ctor.as_int_range().unwrap().clone()).collect();
for (seen, splitted_range) in range_1.split(seen_ranges.iter().cloned()) {
Expand All @@ -1157,10 +1212,6 @@ impl ConstructorSet {
}
}
}

if *non_exhaustive {
missing.push(NonExhaustive);
}
}
&ConstructorSet::Slice(array_len) => {
let seen_slices = seen.map(|c| c.as_slice().unwrap());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
fn main() {
match 0usize {
//~^ ERROR non-exhaustive patterns: `_` not covered
//~| NOTE pattern `_` not covered
//~^ ERROR non-exhaustive patterns: `usize::MAX..` not covered
//~| NOTE pattern `usize::MAX..` not covered
//~| NOTE the matched value is of type `usize`
//~| NOTE `usize` does not have a fixed maximum value
0..=usize::MAX => {}
}

match 0isize {
//~^ ERROR non-exhaustive patterns: `_` not covered
//~| NOTE pattern `_` not covered
//~^ ERROR non-exhaustive patterns: `..isize::MIN` and `isize::MAX..` not covered
//~| NOTE patterns `..isize::MIN` and `isize::MAX..` not covered
//~| NOTE the matched value is of type `isize`
//~| NOTE `isize` does not have a fixed maximum value
//~| NOTE `isize` does not have fixed minimum and maximum values
isize::MIN..=isize::MAX => {}
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
error[E0004]: non-exhaustive patterns: `_` not covered
error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered
--> $DIR/feature-gate-precise_pointer_size_matching.rs:2:11
|
LL | match 0usize {
| ^^^^^^ pattern `_` not covered
| ^^^^^^ pattern `usize::MAX..` not covered
|
= note: the matched value is of type `usize`
= note: `usize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively
= note: `usize` does not have a fixed maximum value, so half-open ranges are necessary to match exhaustively
= help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
LL ~ 0..=usize::MAX => {},
LL + _ => todo!()
LL + usize::MAX.. => todo!()
|

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

error: aborting due to 2 previous errors
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0004]: non-exhaustive patterns: type `usize` is non-empty
--> $DIR/pointer-sized-int.rs:58:11
--> $DIR/pointer-sized-int.rs:54:11
|
LL | match 7usize {}
| ^^^^^^
Expand Down
Loading

0 comments on commit 57cc6aa

Please sign in to comment.