From 11268b48a1dda5e3fad958fbab7f9f829c19de79 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 12 Oct 2023 22:23:38 +0200 Subject: [PATCH 1/9] Add tests --- .../pointer-sized-int.allow.stderr | 2 +- .../pointer-sized-int.deny.stderr | 82 ++++++++++++++++--- .../integer-ranges/pointer-sized-int.rs | 10 +++ 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr index 9f277fa1e1800..12a928bf01803 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr @@ -1,5 +1,5 @@ error[E0004]: non-exhaustive patterns: type `usize` is non-empty - --> $DIR/pointer-sized-int.rs:48:11 + --> $DIR/pointer-sized-int.rs:58:11 | LL | match 7usize {} | ^^^^^^ diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr index df330c60b1e81..7fa9eb8eda664 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr @@ -1,5 +1,5 @@ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:12:11 + --> $DIR/pointer-sized-int.rs:14:11 | LL | match 0usize { | ^^^^^^ pattern `_` not covered @@ -14,7 +14,7 @@ LL + _ => todo!() | error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:17:11 + --> $DIR/pointer-sized-int.rs:19:11 | LL | match 0isize { | ^^^^^^ pattern `_` not covered @@ -29,7 +29,21 @@ LL + _ => todo!() | error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:22:8 + --> $DIR/pointer-sized-int.rs:24:8 + | +LL | m!(0usize, 0..); + | ^^^^^^ pattern `_` 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 + = 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 | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:26:8 | LL | m!(0usize, 0..=usize::MAX); | ^^^^^^ pattern `_` not covered @@ -43,7 +57,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:24:8 + --> $DIR/pointer-sized-int.rs:28:8 | LL | m!(0usize, 0..5 | 5..=usize::MAX); | ^^^^^^ pattern `_` not covered @@ -57,7 +71,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:26:8 + --> $DIR/pointer-sized-int.rs:30:8 | LL | m!(0usize, 0..usize::MAX | usize::MAX); | ^^^^^^ pattern `_` not covered @@ -71,7 +85,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `(_, _)` not covered - --> $DIR/pointer-sized-int.rs:28:8 + --> $DIR/pointer-sized-int.rs:32:8 | LL | m!((0usize, true), (0..5, true) | (5..=usize::MAX, true) | (0..=usize::MAX, false)); | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered @@ -85,7 +99,35 @@ LL | match $s { $($t)+ => {}, (_, _) => todo!() } | +++++++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:31:8 + --> $DIR/pointer-sized-int.rs:34:8 + | +LL | m!(0usize, 0..=usize::MAX | usize::MAX..); + | ^^^^^^ pattern `_` 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 + = 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 | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:37:8 + | +LL | m!(0isize, ..0 | 0..); + | ^^^^^^ pattern `_` 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 + = 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 + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:39:8 | LL | m!(0isize, isize::MIN..=isize::MAX); | ^^^^^^ pattern `_` not covered @@ -99,7 +141,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:33:8 + --> $DIR/pointer-sized-int.rs:41:8 | LL | m!(0isize, isize::MIN..5 | 5..=isize::MAX); | ^^^^^^ pattern `_` not covered @@ -113,7 +155,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:35:8 + --> $DIR/pointer-sized-int.rs:43:8 | LL | m!(0isize, isize::MIN..isize::MAX | isize::MAX); | ^^^^^^ pattern `_` not covered @@ -127,7 +169,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `(_, _)` not covered - --> $DIR/pointer-sized-int.rs:37:8 + --> $DIR/pointer-sized-int.rs:45:8 | LL | m!((0isize, true), (isize::MIN..5, true) | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered @@ -141,7 +183,21 @@ LL | match $s { $($t)+ => {}, (_, _) => todo!() } | +++++++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:41:11 + --> $DIR/pointer-sized-int.rs:48:8 + | +LL | m!(0isize, ..=isize::MIN | isize::MIN..=isize::MAX | isize::MAX..); + | ^^^^^^ pattern `_` 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 + = 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 + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:51:11 | LL | match 0isize { | ^^^^^^ pattern `_` not covered @@ -156,7 +212,7 @@ LL + _ => todo!() | error[E0004]: non-exhaustive patterns: type `usize` is non-empty - --> $DIR/pointer-sized-int.rs:48:11 + --> $DIR/pointer-sized-int.rs:58:11 | LL | match 7usize {} | ^^^^^^ @@ -169,6 +225,6 @@ LL + _ => todo!(), LL + } | -error: aborting due to 12 previous errors +error: aborting due to 16 previous errors For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs index 1ed18c2676358..b55f8f3e51683 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs @@ -1,6 +1,7 @@ // revisions: allow deny #![feature(exclusive_range_pattern)] #![cfg_attr(allow, feature(precise_pointer_size_matching))] +#![allow(overlapping_range_endpoints)] macro_rules! m { ($s:expr, $($t:tt)+) => { @@ -8,6 +9,7 @@ macro_rules! m { } } +#[rustfmt::skip] fn main() { match 0usize { //[deny]~^ ERROR non-exhaustive patterns @@ -19,6 +21,8 @@ fn main() { isize::MIN ..= isize::MAX => {} } + m!(0usize, 0..); + //[deny]~^ ERROR non-exhaustive patterns m!(0usize, 0..=usize::MAX); //[deny]~^ ERROR non-exhaustive patterns m!(0usize, 0..5 | 5..=usize::MAX); @@ -27,7 +31,11 @@ fn main() { //[deny]~^ ERROR non-exhaustive patterns m!((0usize, true), (0..5, true) | (5..=usize::MAX, true) | (0..=usize::MAX, false)); //[deny]~^ ERROR non-exhaustive patterns + m!(0usize, 0..=usize::MAX | usize::MAX..); + //[deny]~^ ERROR non-exhaustive patterns + m!(0isize, ..0 | 0..); + //[deny]~^ ERROR non-exhaustive patterns m!(0isize, isize::MIN..=isize::MAX); //[deny]~^ ERROR non-exhaustive patterns m!(0isize, isize::MIN..5 | 5..=isize::MAX); @@ -37,6 +45,8 @@ fn main() { m!((0isize, true), (isize::MIN..5, true) | (5..=isize::MAX, true) | (isize::MIN..=isize::MAX, false)); //[deny]~^^ ERROR non-exhaustive patterns + m!(0isize, ..=isize::MIN | isize::MIN..=isize::MAX | isize::MAX..); + //[deny]~^ ERROR non-exhaustive patterns match 0isize { //[deny]~^ ERROR non-exhaustive patterns From 8a77b3248f6e147d782ab29915dc1aa093ef7a7c Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 12 Oct 2023 23:29:16 +0200 Subject: [PATCH 2/9] Abstract over `PatRange` boundary value --- compiler/rustc_middle/src/thir.rs | 223 +++++++++++++++++- compiler/rustc_middle/src/ty/util.rs | 78 +++--- .../rustc_mir_build/src/build/matches/mod.rs | 2 +- .../src/build/matches/simplify.rs | 44 +--- .../rustc_mir_build/src/build/matches/test.rs | 47 +--- .../src/thir/pattern/deconstruct_pat.rs | 24 +- .../rustc_mir_build/src/thir/pattern/mod.rs | 94 ++------ 7 files changed, 298 insertions(+), 214 deletions(-) diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index f1747356139dc..fefdce8e77e46 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -16,17 +16,19 @@ use rustc_hir::RangeEnd; use rustc_index::newtype_index; use rustc_index::IndexVec; use rustc_middle::middle::region; -use rustc_middle::mir::interpret::AllocId; +use rustc_middle::mir::interpret::{AllocId, Scalar}; use rustc_middle::mir::{self, BinOp, BorrowKind, FakeReadCause, Mutability, UnOp}; use rustc_middle::ty::adjustment::PointerCoercion; +use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ self, AdtDef, CanonicalUserType, CanonicalUserTypeAnnotation, FnSig, GenericArgsRef, List, Ty, - UpvarArgs, + TyCtxt, UpvarArgs, }; use rustc_span::def_id::LocalDefId; use rustc_span::{sym, ErrorGuaranteed, Span, Symbol, DUMMY_SP}; -use rustc_target::abi::{FieldIdx, VariantIdx}; +use rustc_target::abi::{FieldIdx, Integer, Size, VariantIdx}; use rustc_target::asm::InlineAsmRegOrRegClass; +use std::cmp::Ordering; use std::fmt; use std::ops::Index; @@ -810,12 +812,217 @@ pub enum PatKind<'tcx> { Error(ErrorGuaranteed), } +/// A range pattern. +/// The boundaries must be of the same type and that type must be numeric. #[derive(Clone, Debug, PartialEq, HashStable, TypeVisitable)] pub struct PatRange<'tcx> { - pub lo: mir::Const<'tcx>, - pub hi: mir::Const<'tcx>, + pub lo: PatRangeBoundary<'tcx>, + pub hi: PatRangeBoundary<'tcx>, #[type_visitable(ignore)] pub end: RangeEnd, + pub ty: Ty<'tcx>, +} + +impl<'tcx> PatRange<'tcx> { + /// Whether this range covers the full extent of possible values (best-effort, we ignore floats). + #[inline] + pub fn is_full_range(&self, tcx: TyCtxt<'tcx>) -> Option { + let (min, max, size, bias) = match *self.ty.kind() { + ty::Char => (0, std::char::MAX as u128, Size::from_bits(32), 0), + ty::Int(ity) => { + let size = Integer::from_int_ty(&tcx, ity).size(); + let max = size.truncate(u128::MAX); + let bias = 1u128 << (size.bits() - 1); + (0, max, size, bias) + } + ty::Uint(uty) => { + let size = Integer::from_uint_ty(&tcx, uty).size(); + let max = size.unsigned_int_max(); + (0, max, size, 0) + } + _ => return None, + }; + + // We want to compare ranges numerically, but the order of the bitwise representation of + // signed integers does not match their numeric order. Thus, to correct the ordering, we + // need to shift the range of signed integers to correct the comparison. This is achieved by + // XORing with a bias (see pattern/deconstruct_pat.rs for another pertinent example of this + // pattern). + // + // Also, for performance, it's important to only do the second `try_to_bits` if necessary. + let lo_is_min = match self.lo { + PatRangeBoundary::Finite(value) => { + let lo = value.try_to_bits(size).unwrap() ^ bias; + lo <= min + } + }; + if lo_is_min { + let hi_is_max = match self.hi { + PatRangeBoundary::Finite(value) => { + let hi = value.try_to_bits(size).unwrap() ^ bias; + hi > max || hi == max && self.end == RangeEnd::Included + } + }; + if hi_is_max { + return Some(true); + } + } + Some(false) + } + + #[inline] + pub fn contains( + &self, + value: mir::Const<'tcx>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Option { + use Ordering::*; + debug_assert_eq!(self.ty, value.ty()); + let ty = self.ty; + let value = PatRangeBoundary::Finite(value); + // For performance, it's important to only do the second comparison if necessary. + Some( + match self.lo.compare_with(value, ty, tcx, param_env)? { + Less | Equal => true, + Greater => false, + } && match value.compare_with(self.hi, ty, tcx, param_env)? { + Less => true, + Equal => self.end == RangeEnd::Included, + Greater => false, + }, + ) + } + + #[inline] + pub fn overlaps( + &self, + other: &Self, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Option { + use Ordering::*; + debug_assert_eq!(self.ty, other.ty); + // For performance, it's important to only do the second comparison if necessary. + Some( + match other.lo.compare_with(self.hi, self.ty, tcx, param_env)? { + Less => true, + Equal => self.end == RangeEnd::Included, + Greater => false, + } && match self.lo.compare_with(other.hi, self.ty, tcx, param_env)? { + Less => true, + Equal => other.end == RangeEnd::Included, + Greater => false, + }, + ) + } +} + +impl<'tcx> fmt::Display for PatRange<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let PatRangeBoundary::Finite(value) = &self.lo; + write!(f, "{value}")?; + write!(f, "{}", self.end)?; + let PatRangeBoundary::Finite(value) = &self.hi; + write!(f, "{value}")?; + Ok(()) + } +} + +/// A (possibly open) boundary of a range pattern. +/// If present, the const must be of a numeric type. +#[derive(Copy, Clone, Debug, PartialEq, HashStable, TypeVisitable)] +pub enum PatRangeBoundary<'tcx> { + Finite(mir::Const<'tcx>), +} + +impl<'tcx> PatRangeBoundary<'tcx> { + #[inline] + pub fn lower_bound(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Self { + // Unwrap is ok because the type is known to be numeric. + let c = ty.numeric_min_val(tcx).unwrap(); + let value = mir::Const::from_ty_const(c, tcx); + Self::Finite(value) + } + #[inline] + pub fn upper_bound(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Self { + // Unwrap is ok because the type is known to be numeric. + let c = ty.numeric_max_val(tcx).unwrap(); + let value = mir::Const::from_ty_const(c, tcx); + Self::Finite(value) + } + + #[inline] + pub fn to_const(self, _ty: Ty<'tcx>, _tcx: TyCtxt<'tcx>) -> mir::Const<'tcx> { + match self { + Self::Finite(value) => value, + } + } + pub fn eval_bits( + self, + _ty: Ty<'tcx>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> u128 { + match self { + Self::Finite(value) => value.eval_bits(tcx, param_env), + } + } + + #[instrument(skip(tcx, param_env), level = "debug", ret)] + pub fn compare_with( + self, + other: Self, + ty: Ty<'tcx>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Option { + use PatRangeBoundary::*; + match (self, other) { + // This code is hot when compiling matches with many ranges. So we + // special-case extraction of evaluated scalars for speed, for types where + // raw data comparisons are appropriate. E.g. `unicode-normalization` has + // many ranges such as '\u{037A}'..='\u{037F}', and chars can be compared + // in this way. + (Finite(mir::Const::Ty(a)), Finite(mir::Const::Ty(b))) + if matches!(ty.kind(), ty::Uint(_) | ty::Char) => + { + return Some(a.kind().cmp(&b.kind())); + } + ( + Finite(mir::Const::Val(mir::ConstValue::Scalar(Scalar::Int(a)), _)), + Finite(mir::Const::Val(mir::ConstValue::Scalar(Scalar::Int(b)), _)), + ) if matches!(ty.kind(), ty::Uint(_) | ty::Char) => return Some(a.cmp(&b)), + _ => {} + } + + let a = self.eval_bits(ty, tcx, param_env); + let b = other.eval_bits(ty, tcx, param_env); + + match ty.kind() { + ty::Float(ty::FloatTy::F32) => { + use rustc_apfloat::Float; + let a = rustc_apfloat::ieee::Single::from_bits(a); + let b = rustc_apfloat::ieee::Single::from_bits(b); + a.partial_cmp(&b) + } + ty::Float(ty::FloatTy::F64) => { + use rustc_apfloat::Float; + let a = rustc_apfloat::ieee::Double::from_bits(a); + let b = rustc_apfloat::ieee::Double::from_bits(b); + a.partial_cmp(&b) + } + ty::Int(ity) => { + use rustc_middle::ty::layout::IntegerExt; + let size = rustc_target::abi::Integer::from_int_ty(&tcx, *ity).size(); + let a = size.sign_extend(a) as i128; + let b = size.sign_extend(b) as i128; + Some(a.cmp(&b)) + } + ty::Uint(_) | ty::Char => Some(a.cmp(&b)), + _ => bug!(), + } + } } impl<'tcx> fmt::Display for Pat<'tcx> { @@ -944,11 +1151,7 @@ impl<'tcx> fmt::Display for Pat<'tcx> { PatKind::InlineConstant { def: _, ref subpattern } => { write!(f, "{} (from inline const)", subpattern) } - PatKind::Range(box PatRange { lo, hi, end }) => { - write!(f, "{lo}")?; - write!(f, "{end}")?; - write!(f, "{hi}") - } + PatKind::Range(ref range) => write!(f, "{range}"), PatKind::Slice { ref prefix, ref slice, ref suffix } | PatKind::Array { ref prefix, ref slice, ref suffix } => { write!(f, "[")?; diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index be48c0e8926b7..a2a74dafc5f04 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -19,7 +19,7 @@ use rustc_index::bit_set::GrowableBitSet; use rustc_macros::HashStable; use rustc_session::Limit; use rustc_span::sym; -use rustc_target::abi::{Integer, IntegerType, Size}; +use rustc_target::abi::{Integer, IntegerType, Primitive, Size}; use rustc_target::spec::abi::Abi; use smallvec::SmallVec; use std::{fmt, iter}; @@ -917,54 +917,62 @@ impl<'tcx> TypeFolder> for OpaqueTypeExpander<'tcx> { } impl<'tcx> Ty<'tcx> { + /// Returns the `Size` for primitive types (bool, uint, int, char, float). + pub fn primitive_size(self, tcx: TyCtxt<'tcx>) -> Size { + match *self.kind() { + ty::Bool => Size::from_bytes(1), + ty::Char => Size::from_bytes(4), + ty::Int(ity) => Integer::from_int_ty(&tcx, ity).size(), + ty::Uint(uty) => Integer::from_uint_ty(&tcx, uty).size(), + ty::Float(ty::FloatTy::F32) => Primitive::F32.size(&tcx), + ty::Float(ty::FloatTy::F64) => Primitive::F64.size(&tcx), + _ => bug!("non primitive type"), + } + } + pub fn int_size_and_signed(self, tcx: TyCtxt<'tcx>) -> (Size, bool) { - let (int, signed) = match *self.kind() { - ty::Int(ity) => (Integer::from_int_ty(&tcx, ity), true), - ty::Uint(uty) => (Integer::from_uint_ty(&tcx, uty), false), + match *self.kind() { + ty::Int(ity) => (Integer::from_int_ty(&tcx, ity).size(), true), + ty::Uint(uty) => (Integer::from_uint_ty(&tcx, uty).size(), false), _ => bug!("non integer discriminant"), - }; - (int.size(), signed) + } } - /// Returns the maximum value for the given numeric type (including `char`s) - /// or returns `None` if the type is not numeric. - pub fn numeric_max_val(self, tcx: TyCtxt<'tcx>) -> Option> { - let val = match self.kind() { + /// Returns the minimum and maximum values for the given numeric type (including `char`s) or + /// returns `None` if the type is not numeric. + pub fn numeric_min_and_max_as_bits(self, tcx: TyCtxt<'tcx>) -> Option<(u128, u128)> { + use rustc_apfloat::ieee::{Double, Single}; + Some(match self.kind() { ty::Int(_) | ty::Uint(_) => { let (size, signed) = self.int_size_and_signed(tcx); - let val = + let min = if signed { size.truncate(size.signed_int_min() as u128) } else { 0 }; + let max = if signed { size.signed_int_max() as u128 } else { size.unsigned_int_max() }; - Some(val) + (min, max) } - ty::Char => Some(std::char::MAX as u128), - ty::Float(fty) => Some(match fty { - ty::FloatTy::F32 => rustc_apfloat::ieee::Single::INFINITY.to_bits(), - ty::FloatTy::F64 => rustc_apfloat::ieee::Double::INFINITY.to_bits(), - }), - _ => None, - }; + ty::Char => (0, std::char::MAX as u128), + ty::Float(ty::FloatTy::F32) => { + ((-Single::INFINITY).to_bits(), Single::INFINITY.to_bits()) + } + ty::Float(ty::FloatTy::F64) => { + ((-Double::INFINITY).to_bits(), Double::INFINITY.to_bits()) + } + _ => return None, + }) + } - val.map(|v| ty::Const::from_bits(tcx, v, ty::ParamEnv::empty().and(self))) + /// Returns the maximum value for the given numeric type (including `char`s) + /// or returns `None` if the type is not numeric. + pub fn numeric_max_val(self, tcx: TyCtxt<'tcx>) -> Option> { + self.numeric_min_and_max_as_bits(tcx) + .map(|(_, max)| ty::Const::from_bits(tcx, max, ty::ParamEnv::empty().and(self))) } /// Returns the minimum value for the given numeric type (including `char`s) /// or returns `None` if the type is not numeric. pub fn numeric_min_val(self, tcx: TyCtxt<'tcx>) -> Option> { - let val = match self.kind() { - ty::Int(_) | ty::Uint(_) => { - let (size, signed) = self.int_size_and_signed(tcx); - let val = if signed { size.truncate(size.signed_int_min() as u128) } else { 0 }; - Some(val) - } - ty::Char => Some(0), - ty::Float(fty) => Some(match fty { - ty::FloatTy::F32 => (-::rustc_apfloat::ieee::Single::INFINITY).to_bits(), - ty::FloatTy::F64 => (-::rustc_apfloat::ieee::Double::INFINITY).to_bits(), - }), - _ => None, - }; - - val.map(|v| ty::Const::from_bits(tcx, v, ty::ParamEnv::empty().and(self))) + self.numeric_min_and_max_as_bits(tcx) + .map(|(min, _)| ty::Const::from_bits(tcx, min, ty::ParamEnv::empty().and(self))) } /// Checks whether values of this type `T` are *moved* or *copied* diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 1cf8c202ea4be..2e30cdd2c390e 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1027,7 +1027,7 @@ enum TestKind<'tcx> { ty: Ty<'tcx>, }, - /// Test whether the value falls within an inclusive or exclusive range + /// Test whether the value falls within an inclusive or exclusive range. Range(Box>), /// Test that the length of the slice is equal to `len`. diff --git a/compiler/rustc_mir_build/src/build/matches/simplify.rs b/compiler/rustc_mir_build/src/build/matches/simplify.rs index 32573b4d53afd..6a40c8d840bd5 100644 --- a/compiler/rustc_mir_build/src/build/matches/simplify.rs +++ b/compiler/rustc_mir_build/src/build/matches/simplify.rs @@ -15,11 +15,7 @@ use crate::build::expr::as_place::PlaceBuilder; use crate::build::matches::{Ascription, Binding, Candidate, MatchPair}; use crate::build::Builder; -use rustc_hir::RangeEnd; use rustc_middle::thir::{self, *}; -use rustc_middle::ty; -use rustc_middle::ty::layout::IntegerExt; -use rustc_target::abi::{Integer, Size}; use std::mem; @@ -148,7 +144,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { match_pair: MatchPair<'pat, 'tcx>, candidate: &mut Candidate<'pat, 'tcx>, ) -> Result<(), MatchPair<'pat, 'tcx>> { - let tcx = self.tcx; match match_pair.pattern.kind { PatKind::AscribeUserType { ref subpattern, @@ -210,41 +205,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { Ok(()) } - PatKind::Range(box PatRange { lo, hi, end }) => { - let (range, bias) = match *lo.ty().kind() { - ty::Char => { - (Some(('\u{0000}' as u128, '\u{10FFFF}' as u128, Size::from_bits(32))), 0) - } - ty::Int(ity) => { - let size = Integer::from_int_ty(&tcx, ity).size(); - let max = size.truncate(u128::MAX); - let bias = 1u128 << (size.bits() - 1); - (Some((0, max, size)), bias) - } - ty::Uint(uty) => { - let size = Integer::from_uint_ty(&tcx, uty).size(); - let max = size.truncate(u128::MAX); - (Some((0, max, size)), 0) - } - _ => (None, 0), - }; - if let Some((min, max, sz)) = range { - // We want to compare ranges numerically, but the order of the bitwise - // representation of signed integers does not match their numeric order. Thus, - // to correct the ordering, we need to shift the range of signed integers to - // correct the comparison. This is achieved by XORing with a bias (see - // pattern/_match.rs for another pertinent example of this pattern). - // - // Also, for performance, it's important to only do the second - // `try_to_bits` if necessary. - let lo = lo.try_to_bits(sz).unwrap() ^ bias; - if lo <= min { - let hi = hi.try_to_bits(sz).unwrap() ^ bias; - if hi > max || hi == max && end == RangeEnd::Included { - // Irrefutable pattern match. - return Ok(()); - } - } + PatKind::Range(ref range) => { + if let Some(true) = range.is_full_range(self.tcx) { + // Irrefutable pattern match. + return Ok(()); } Err(match_pair) } diff --git a/compiler/rustc_mir_build/src/build/matches/test.rs b/compiler/rustc_mir_build/src/build/matches/test.rs index 5e7db7413df4f..bdd4f2011ebe6 100644 --- a/compiler/rustc_mir_build/src/build/matches/test.rs +++ b/compiler/rustc_mir_build/src/build/matches/test.rs @@ -8,7 +8,6 @@ use crate::build::expr::as_place::PlaceBuilder; use crate::build::matches::{Candidate, MatchPair, Test, TestKind}; use crate::build::Builder; -use crate::thir::pattern::compare_const_vals; use rustc_data_structures::fx::FxIndexMap; use rustc_hir::{LangItem, RangeEnd}; use rustc_index::bit_set::BitSet; @@ -59,8 +58,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { }, PatKind::Range(ref range) => { - assert_eq!(range.lo.ty(), match_pair.pattern.ty); - assert_eq!(range.hi.ty(), match_pair.pattern.ty); + assert_eq!(range.ty, match_pair.pattern.ty); Test { span: match_pair.pattern.span, kind: TestKind::Range(range.clone()) } } @@ -309,11 +307,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } - TestKind::Range(box PatRange { lo, hi, ref end }) => { + TestKind::Range(ref range) => { let lower_bound_success = self.cfg.start_new_block(); let target_blocks = make_target_blocks(self); // Test `val` by computing `lo <= val && val <= hi`, using primitive comparisons. + // FIXME: skip useless comparison when the range is half-open. + let lo = range.lo.to_const(range.ty, self.tcx); + let hi = range.hi.to_const(range.ty, self.tcx); let lo = self.literal_operand(test.span, lo); let hi = self.literal_operand(test.span, hi); let val = Operand::Copy(place); @@ -330,7 +331,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { lo, val.clone(), ); - let op = match *end { + let op = match range.end { RangeEnd::Included => BinOp::Le, RangeEnd::Excluded => BinOp::Lt, }; @@ -698,34 +699,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } (TestKind::Range(test), PatKind::Range(pat)) => { - use std::cmp::Ordering::*; - if test == pat { self.candidate_without_match_pair(match_pair_index, candidate); return Some(0); } - // For performance, it's important to only do the second - // `compare_const_vals` if necessary. - let no_overlap = if matches!( - (compare_const_vals(self.tcx, test.hi, pat.lo, self.param_env)?, test.end), - (Less, _) | (Equal, RangeEnd::Excluded) // test < pat - ) || matches!( - (compare_const_vals(self.tcx, test.lo, pat.hi, self.param_env)?, pat.end), - (Greater, _) | (Equal, RangeEnd::Excluded) // test > pat - ) { - Some(1) - } else { - None - }; - // If the testing range does not overlap with pattern range, // the pattern can be matched only if this test fails. - no_overlap + if !test.overlaps(pat, self.tcx, self.param_env)? { Some(1) } else { None } } (TestKind::Range(range), &PatKind::Constant { value }) => { - if let Some(false) = self.const_range_contains(&*range, value) { + if !range.contains(value, self.tcx, self.param_env)? { // `value` is not contained in the testing range, // so `value` can be matched only if this test fails. Some(1) @@ -817,27 +802,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { span_bug!(match_pair.pattern.span, "simplifiable pattern found: {:?}", match_pair.pattern) } - fn const_range_contains(&self, range: &PatRange<'tcx>, value: Const<'tcx>) -> Option { - use std::cmp::Ordering::*; - - // For performance, it's important to only do the second - // `compare_const_vals` if necessary. - Some( - matches!(compare_const_vals(self.tcx, range.lo, value, self.param_env)?, Less | Equal) - && matches!( - (compare_const_vals(self.tcx, value, range.hi, self.param_env)?, range.end), - (Less, _) | (Equal, RangeEnd::Included) - ), - ) - } - fn values_not_contained_in_range( &self, range: &PatRange<'tcx>, options: &FxIndexMap, u128>, ) -> Option { for &val in options.keys() { - if self.const_range_contains(range, val)? { + if range.contains(val, self.tcx, self.param_env)? { return Some(false); } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 186c77795e494..f93daa29afb5a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -57,7 +57,7 @@ use rustc_hir::RangeEnd; use rustc_index::Idx; use rustc_middle::middle::stability::EvalResult; use rustc_middle::mir; -use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange}; +use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange, PatRangeBoundary}; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef}; use rustc_span::{Span, DUMMY_SP}; @@ -278,19 +278,20 @@ impl IntRange { let (lo, hi) = self.boundaries(); let bias = IntRange::signed_bias(tcx, ty); - let (lo, hi) = (lo ^ bias, hi ^ bias); + let (lo_bits, hi_bits) = (lo ^ bias, hi ^ bias); let env = ty::ParamEnv::empty().and(ty); - let lo_const = mir::Const::from_bits(tcx, lo, env); - let hi_const = mir::Const::from_bits(tcx, hi, env); + let lo_const = mir::Const::from_bits(tcx, lo_bits, env); + let hi_const = mir::Const::from_bits(tcx, hi_bits, env); - let kind = if lo == hi { + let kind = if lo_bits == hi_bits { PatKind::Constant { value: lo_const } } else { PatKind::Range(Box::new(PatRange { - lo: lo_const, - hi: hi_const, + lo: PatRangeBoundary::Finite(lo_const), + hi: PatRangeBoundary::Finite(hi_const), end: RangeEnd::Included, + ty, })) }; @@ -1387,11 +1388,12 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { } } } - PatKind::Range(box PatRange { lo, hi, end }) => { + PatKind::Range(box PatRange { lo, hi, end, .. }) => { use rustc_apfloat::Float; - let ty = lo.ty(); - let lo = lo.try_eval_bits(cx.tcx, cx.param_env).unwrap(); - let hi = hi.try_eval_bits(cx.tcx, cx.param_env).unwrap(); + let ty = pat.ty; + // FIXME: handle half-open ranges + let lo = lo.eval_bits(ty, cx.tcx, cx.param_env); + let hi = hi.eval_bits(ty, cx.tcx, cx.param_env); ctor = match ty.kind() { ty::Char | ty::Int(_) | ty::Uint(_) => { IntRange(IntRange::from_range(cx.tcx, lo, hi, ty, *end)) diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index dd71ab1f8e557..c530c0a772c91 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -17,11 +17,11 @@ use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::pat_util::EnumerateAndAdjustIterator; use rustc_hir::RangeEnd; use rustc_index::Idx; -use rustc_middle::mir::interpret::{ - ErrorHandled, GlobalId, LitToConstError, LitToConstInput, Scalar, -}; +use rustc_middle::mir::interpret::{ErrorHandled, GlobalId, LitToConstError, LitToConstInput}; use rustc_middle::mir::{self, BorrowKind, Const, Mutability, UserTypeProjection}; -use rustc_middle::thir::{Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange}; +use rustc_middle::thir::{ + Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange, PatRangeBoundary, +}; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ self, AdtDef, CanonicalUserTypeAnnotation, GenericArg, GenericArgsRef, Region, Ty, TyCtxt, @@ -90,7 +90,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { &mut self, expr: Option<&'tcx hir::Expr<'tcx>>, ) -> Result< - (Option>, Option>, Option), + (Option>, Option>, Option), ErrorGuaranteed, > { match expr { @@ -113,7 +113,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { ); return Err(self.tcx.sess.delay_span_bug(expr.span, msg)); }; - Ok((Some(value), ascr, inline_const)) + Ok((Some(PatRangeBoundary::Finite(value)), ascr, inline_const)) } } } @@ -187,31 +187,23 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { let (lo, lo_ascr, lo_inline) = self.lower_pattern_range_endpoint(lo_expr)?; let (hi, hi_ascr, hi_inline) = self.lower_pattern_range_endpoint(hi_expr)?; - let lo = lo.unwrap_or_else(|| { - // Unwrap is ok because the type is known to be numeric. - let lo = ty.numeric_min_val(self.tcx).unwrap(); - mir::Const::from_ty_const(lo, self.tcx) - }); - let hi = hi.unwrap_or_else(|| { - // Unwrap is ok because the type is known to be numeric. - let hi = ty.numeric_max_val(self.tcx).unwrap(); - mir::Const::from_ty_const(hi, self.tcx) - }); - assert_eq!(lo.ty(), ty); - assert_eq!(hi.ty(), ty); - - let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env); + let lo = lo.unwrap_or_else(|| PatRangeBoundary::lower_bound(ty, self.tcx)); + let hi = hi.unwrap_or_else(|| PatRangeBoundary::upper_bound(ty, self.tcx)); + + let cmp = lo.compare_with(hi, ty, self.tcx, self.param_env); let mut kind = match (end, cmp) { // `x..y` where `x < y`. // Non-empty because the range includes at least `x`. (RangeEnd::Excluded, Some(Ordering::Less)) => { - PatKind::Range(Box::new(PatRange { lo, hi, end })) + PatKind::Range(Box::new(PatRange { lo, hi, end, ty })) } // `x..=y` where `x == y`. - (RangeEnd::Included, Some(Ordering::Equal)) => PatKind::Constant { value: lo }, + (RangeEnd::Included, Some(Ordering::Equal)) => { + PatKind::Constant { value: lo.to_const(ty, self.tcx) } + } // `x..=y` where `x < y`. (RangeEnd::Included, Some(Ordering::Less)) => { - PatKind::Range(Box::new(PatRange { lo, hi, end })) + PatKind::Range(Box::new(PatRange { lo, hi, end, ty })) } // `x..y` where `x >= y`, or `x..=y` where `x > y`. The range is empty => error. _ => { @@ -851,59 +843,3 @@ impl<'tcx> PatternFoldable<'tcx> for PatKind<'tcx> { } } } - -#[instrument(skip(tcx), level = "debug")] -pub(crate) fn compare_const_vals<'tcx>( - tcx: TyCtxt<'tcx>, - a: mir::Const<'tcx>, - b: mir::Const<'tcx>, - param_env: ty::ParamEnv<'tcx>, -) -> Option { - assert_eq!(a.ty(), b.ty()); - - let ty = a.ty(); - - // This code is hot when compiling matches with many ranges. So we - // special-case extraction of evaluated scalars for speed, for types where - // raw data comparisons are appropriate. E.g. `unicode-normalization` has - // many ranges such as '\u{037A}'..='\u{037F}', and chars can be compared - // in this way. - match ty.kind() { - ty::Float(_) | ty::Int(_) => {} // require special handling, see below - _ => match (a, b) { - ( - mir::Const::Val(mir::ConstValue::Scalar(Scalar::Int(a)), _a_ty), - mir::Const::Val(mir::ConstValue::Scalar(Scalar::Int(b)), _b_ty), - ) => return Some(a.cmp(&b)), - (mir::Const::Ty(a), mir::Const::Ty(b)) => { - return Some(a.kind().cmp(&b.kind())); - } - _ => {} - }, - } - - let a = a.eval_bits(tcx, param_env); - let b = b.eval_bits(tcx, param_env); - - use rustc_apfloat::Float; - match *ty.kind() { - ty::Float(ty::FloatTy::F32) => { - let a = rustc_apfloat::ieee::Single::from_bits(a); - let b = rustc_apfloat::ieee::Single::from_bits(b); - a.partial_cmp(&b) - } - ty::Float(ty::FloatTy::F64) => { - let a = rustc_apfloat::ieee::Double::from_bits(a); - let b = rustc_apfloat::ieee::Double::from_bits(b); - a.partial_cmp(&b) - } - ty::Int(ity) => { - use rustc_middle::ty::layout::IntegerExt; - let size = rustc_target::abi::Integer::from_int_ty(&tcx, ity).size(); - let a = size.sign_extend(a); - let b = size.sign_extend(b); - Some((a as i128).cmp(&(b as i128))) - } - _ => Some(a.cmp(&b)), - } -} From 0ba6c4ab676c7845de2d3b9209139e02cbfe8fae Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 13 Oct 2023 01:22:37 +0200 Subject: [PATCH 3/9] Propagate half-open ranges through THIR --- compiler/rustc_middle/src/thir.rs | 72 +++++++++++++------ .../rustc_mir_build/src/thir/pattern/mod.rs | 31 ++++---- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index fefdce8e77e46..9457b62f22c1f 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -851,17 +851,21 @@ impl<'tcx> PatRange<'tcx> { // // Also, for performance, it's important to only do the second `try_to_bits` if necessary. let lo_is_min = match self.lo { + PatRangeBoundary::NegInfinity => true, PatRangeBoundary::Finite(value) => { let lo = value.try_to_bits(size).unwrap() ^ bias; lo <= min } + PatRangeBoundary::PosInfinity => false, }; if lo_is_min { let hi_is_max = match self.hi { + PatRangeBoundary::NegInfinity => false, PatRangeBoundary::Finite(value) => { let hi = value.try_to_bits(size).unwrap() ^ bias; hi > max || hi == max && self.end == RangeEnd::Included } + PatRangeBoundary::PosInfinity => true, }; if hi_is_max { return Some(true); @@ -920,11 +924,16 @@ impl<'tcx> PatRange<'tcx> { impl<'tcx> fmt::Display for PatRange<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let PatRangeBoundary::Finite(value) = &self.lo; - write!(f, "{value}")?; - write!(f, "{}", self.end)?; - let PatRangeBoundary::Finite(value) = &self.hi; - write!(f, "{value}")?; + if let PatRangeBoundary::Finite(value) = &self.lo { + write!(f, "{value}")?; + } + if let PatRangeBoundary::Finite(value) = &self.hi { + write!(f, "{}", self.end)?; + write!(f, "{value}")?; + } else { + // `0..` is parsed as an inclusive range, we must display it correctly. + write!(f, "..")?; + } Ok(()) } } @@ -934,38 +943,49 @@ impl<'tcx> fmt::Display for PatRange<'tcx> { #[derive(Copy, Clone, Debug, PartialEq, HashStable, TypeVisitable)] pub enum PatRangeBoundary<'tcx> { Finite(mir::Const<'tcx>), + NegInfinity, + PosInfinity, } impl<'tcx> PatRangeBoundary<'tcx> { #[inline] - pub fn lower_bound(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Self { - // Unwrap is ok because the type is known to be numeric. - let c = ty.numeric_min_val(tcx).unwrap(); - let value = mir::Const::from_ty_const(c, tcx); - Self::Finite(value) + pub fn is_finite(self) -> bool { + matches!(self, Self::Finite(..)) } #[inline] - pub fn upper_bound(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Self { - // Unwrap is ok because the type is known to be numeric. - let c = ty.numeric_max_val(tcx).unwrap(); - let value = mir::Const::from_ty_const(c, tcx); - Self::Finite(value) + pub fn as_finite(self) -> Option> { + match self { + Self::Finite(value) => Some(value), + Self::NegInfinity | Self::PosInfinity => None, + } } - #[inline] - pub fn to_const(self, _ty: Ty<'tcx>, _tcx: TyCtxt<'tcx>) -> mir::Const<'tcx> { + pub fn to_const(self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> mir::Const<'tcx> { match self { Self::Finite(value) => value, + Self::NegInfinity => { + // Unwrap is ok because the type is known to be numeric. + let c = ty.numeric_min_val(tcx).unwrap(); + mir::Const::from_ty_const(c, tcx) + } + Self::PosInfinity => { + // Unwrap is ok because the type is known to be numeric. + let c = ty.numeric_max_val(tcx).unwrap(); + mir::Const::from_ty_const(c, tcx) + } } } - pub fn eval_bits( - self, - _ty: Ty<'tcx>, - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - ) -> u128 { + pub fn eval_bits(self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> u128 { match self { Self::Finite(value) => value.eval_bits(tcx, param_env), + Self::NegInfinity => { + // Unwrap is ok because the type is known to be numeric. + ty.numeric_min_and_max_as_bits(tcx).unwrap().0 + } + Self::PosInfinity => { + // Unwrap is ok because the type is known to be numeric. + ty.numeric_min_and_max_as_bits(tcx).unwrap().1 + } } } @@ -979,6 +999,12 @@ impl<'tcx> PatRangeBoundary<'tcx> { ) -> Option { use PatRangeBoundary::*; match (self, other) { + // When comparing with infinities, we must remember that `0u8..` and `0u8..=255` + // describe the same range. These two shortcuts are ok, but for the rest we must check + // bit values. + (PosInfinity, PosInfinity) => return Some(Ordering::Equal), + (NegInfinity, NegInfinity) => return Some(Ordering::Equal), + // This code is hot when compiling matches with many ranges. So we // special-case extraction of evaluated scalars for speed, for types where // raw data comparisons are appropriate. E.g. `unicode-normalization` has diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index c530c0a772c91..0811ab6a0a6bd 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -187,24 +187,25 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { let (lo, lo_ascr, lo_inline) = self.lower_pattern_range_endpoint(lo_expr)?; let (hi, hi_ascr, hi_inline) = self.lower_pattern_range_endpoint(hi_expr)?; - let lo = lo.unwrap_or_else(|| PatRangeBoundary::lower_bound(ty, self.tcx)); - let hi = hi.unwrap_or_else(|| PatRangeBoundary::upper_bound(ty, self.tcx)); + let lo = lo.unwrap_or(PatRangeBoundary::NegInfinity); + let hi = hi.unwrap_or(PatRangeBoundary::PosInfinity); let cmp = lo.compare_with(hi, ty, self.tcx, self.param_env); - let mut kind = match (end, cmp) { + let mut kind = PatKind::Range(Box::new(PatRange { lo, hi, end, ty })); + match (end, cmp) { // `x..y` where `x < y`. - // Non-empty because the range includes at least `x`. - (RangeEnd::Excluded, Some(Ordering::Less)) => { - PatKind::Range(Box::new(PatRange { lo, hi, end, ty })) - } - // `x..=y` where `x == y`. - (RangeEnd::Included, Some(Ordering::Equal)) => { - PatKind::Constant { value: lo.to_const(ty, self.tcx) } - } + (RangeEnd::Excluded, Some(Ordering::Less)) => {} // `x..=y` where `x < y`. - (RangeEnd::Included, Some(Ordering::Less)) => { - PatKind::Range(Box::new(PatRange { lo, hi, end, ty })) - } + (RangeEnd::Included, Some(Ordering::Less)) => {} + // `x..=y` where `x == y` and `x` and `y` are finite. + (RangeEnd::Included, Some(Ordering::Equal)) if lo.is_finite() && hi.is_finite() => { + kind = PatKind::Constant { value: lo.as_finite().unwrap() }; + } + // `..=x` where `x == ty::MIN`. + (RangeEnd::Included, Some(Ordering::Equal)) if !lo.is_finite() => {} + // `x..` where `x == ty::MAX` (yes, `x..` gives `RangeEnd::Included` since it is meant + // to include `ty::MAX`). + (RangeEnd::Included, Some(Ordering::Equal)) if !hi.is_finite() => {} // `x..y` where `x >= y`, or `x..=y` where `x > y`. The range is empty => error. _ => { // Emit a more appropriate message if there was overflow. @@ -223,7 +224,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { }; return Err(e); } - }; + } // If we are handling a range with associated constants (e.g. // `Foo::<'a>::A..=Foo::B`), we need to put the ascriptions for the associated From a5c67f4107530506c7bc1752cfc7437fa7ff933a Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 12 Oct 2023 16:51:27 +0200 Subject: [PATCH 4/9] Don't use `IntRange` for booleans --- .../src/thir/pattern/deconstruct_pat.rs | 70 +++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index f93daa29afb5a..c13307aa419b5 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -110,7 +110,7 @@ pub(crate) struct IntRange { impl IntRange { #[inline] pub(super) fn is_integral(ty: Ty<'_>) -> bool { - matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_) | ty::Bool) + matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_)) } pub(super) fn is_singleton(&self) -> bool { @@ -299,8 +299,8 @@ impl IntRange { } } -/// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and -/// would be displayed as such. To render properly, convert to a pattern first. +/// Note: this will render signed ranges incorrectly. To render properly, convert to a pattern +/// first. impl fmt::Debug for IntRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (lo, hi) = self.boundaries(); @@ -541,6 +541,8 @@ pub(super) enum Constructor<'tcx> { Single, /// Enum variants. Variant(VariantIdx), + /// Booleans + Bool(bool), /// Ranges of integer literal values (`2`, `2..=5` or `2..5`). IntRange(IntRange), /// Ranges of floating-point literal values (`2.0..=5.2`). @@ -581,6 +583,12 @@ impl<'tcx> Constructor<'tcx> { _ => None, } } + fn as_bool(&self) -> Option { + match self { + Bool(b) => Some(*b), + _ => None, + } + } pub(super) fn as_int_range(&self) -> Option<&IntRange> { match self { IntRange(range) => Some(range), @@ -625,10 +633,11 @@ impl<'tcx> Constructor<'tcx> { _ => bug!("Unexpected type for `Single` constructor: {:?}", pcx.ty), }, Slice(slice) => slice.arity(), - Str(..) + Bool(..) + | IntRange(..) | F32Range(..) | F64Range(..) - | IntRange(..) + | Str(..) | Opaque | NonExhaustive | Hidden @@ -744,6 +753,7 @@ impl<'tcx> Constructor<'tcx> { (Single, Single) => true, (Variant(self_id), Variant(other_id)) => self_id == other_id, + (Bool(self_b), Bool(other_b)) => self_b == other_b, (IntRange(self_range), IntRange(other_range)) => self_range.is_subrange(other_range), (F32Range(self_from, self_to, self_end), F32Range(other_from, other_to, other_end)) => { @@ -796,9 +806,10 @@ pub(super) enum ConstructorSet { hidden_variants: Vec, non_exhaustive: bool, }, + /// Booleans. + 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`. - /// This is reused for bool. FIXME: don't. /// `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, non_exhaustive: bool }, @@ -848,9 +859,7 @@ impl ConstructorSet { // Invariant: this is `Uninhabited` if and only if the type is uninhabited (as determined by // `cx.is_uninhabited()`). match ty.kind() { - ty::Bool => { - Self::Integers { range_1: make_range(0, 1), range_2: None, non_exhaustive: false } - } + ty::Bool => Self::Bool, ty::Char => { // The valid Unicode Scalar Value ranges. Self::Integers { @@ -1010,6 +1019,27 @@ impl ConstructorSet { missing.push(NonExhaustive); } } + ConstructorSet::Bool => { + let mut seen_false = false; + let mut seen_true = false; + for b in seen.map(|ctor| ctor.as_bool().unwrap()) { + if b { + seen_true = true; + } else { + seen_false = true; + } + } + if seen_false { + present.push(Bool(false)); + } else { + missing.push(Bool(false)); + } + if seen_true { + present.push(Bool(true)); + } else { + missing.push(Bool(true)); + } + } ConstructorSet::Integers { range_1, range_2, non_exhaustive } => { let seen_ranges: Vec<_> = seen.map(|ctor| ctor.as_int_range().unwrap().clone()).collect(); @@ -1205,10 +1235,11 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { } _ => bug!("bad slice pattern {:?} {:?}", constructor, pcx), }, - Str(..) + Bool(..) + | IntRange(..) | F32Range(..) | F64Range(..) - | IntRange(..) + | Str(..) | Opaque | NonExhaustive | Hidden @@ -1337,7 +1368,14 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { } PatKind::Constant { value } => { match pat.ty.kind() { - ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) => { + ty::Bool => { + ctor = match value.try_eval_bool(cx.tcx, cx.param_env) { + Some(b) => Bool(b), + None => Opaque, + }; + fields = Fields::empty(); + } + ty::Char | ty::Int(_) | ty::Uint(_) => { ctor = match value.try_eval_bits(cx.tcx, cx.param_env) { Some(bits) => IntRange(IntRange::from_bits(cx.tcx, pat.ty, bits)), None => Opaque, @@ -1616,9 +1654,11 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> { } write!(f, "]") } + Bool(b) => write!(f, "{b}"), + // Best-effort, will render signed ranges incorrectly + IntRange(range) => write!(f, "{range:?}"), F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"), F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"), - IntRange(range) => write!(f, "{range:?}"), // Best-effort, will render e.g. `false` as `0..=0` Str(value) => write!(f, "{value}"), Opaque => write!(f, ""), Or => { @@ -1668,10 +1708,13 @@ impl<'tcx> WitnessPat<'tcx> { self.ty } + /// Convert back to a `thir::Pat` for diagnostic purposes. pub(crate) fn to_pat(&self, cx: &MatchCheckCtxt<'_, 'tcx>) -> Pat<'tcx> { let is_wildcard = |pat: &Pat<'_>| matches!(pat.kind, PatKind::Wild); let mut subpatterns = self.iter_fields().map(|p| Box::new(p.to_pat(cx))); let kind = match &self.ctor { + Bool(b) => PatKind::Constant { value: mir::Const::from_bool(cx.tcx, *b) }, + IntRange(range) => return range.to_pat(cx.tcx, self.ty), Single | Variant(_) => match self.ty.kind() { ty::Tuple(..) => PatKind::Leaf { subpatterns: subpatterns @@ -1741,7 +1784,6 @@ impl<'tcx> WitnessPat<'tcx> { } } &Str(value) => PatKind::Constant { value }, - IntRange(range) => return range.to_pat(cx.tcx, self.ty), Wildcard | NonExhaustive | Hidden => PatKind::Wild, Missing { .. } => bug!( "trying to convert a `Missing` constructor into a `Pat`; this is probably a bug, From 9bc4c378ab3bbdd330ccd1268fae478a0f0905f2 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 12 Oct 2023 16:19:02 +0200 Subject: [PATCH 5/9] Inline `RangeInclusive` into `IntRange` --- .../src/thir/pattern/deconstruct_pat.rs | 56 ++++++++----------- .../src/thir/pattern/usefulness.rs | 18 +++--- 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index c13307aa419b5..249273c3595f5 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -46,7 +46,6 @@ use std::cell::Cell; use std::cmp::{self, max, min, Ordering}; use std::fmt; use std::iter::once; -use std::ops::RangeInclusive; use smallvec::{smallvec, SmallVec}; @@ -102,9 +101,10 @@ enum Presence { /// /// `IntRange` is never used to encode an empty range or a "range" that wraps /// around the (offset) space: i.e., `range.lo <= range.hi`. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub(crate) struct IntRange { - range: RangeInclusive, + pub(crate) lo: u128, + pub(crate) hi: u128, } impl IntRange { @@ -114,20 +114,16 @@ impl IntRange { } pub(super) fn is_singleton(&self) -> bool { - self.range.start() == self.range.end() - } - - pub(super) fn boundaries(&self) -> (u128, u128) { - (*self.range.start(), *self.range.end()) + self.lo == self.hi } #[inline] fn from_bits<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, bits: u128) -> IntRange { let bias = IntRange::signed_bias(tcx, ty); - // Perform a shift if the underlying types are signed, - // which makes the interval arithmetic simpler. + // Perform a shift if the underlying types are signed, which makes the interval arithmetic + // type-independent. let val = bits ^ bias; - IntRange { range: val..=val } + IntRange { lo: val, hi: val } } #[inline] @@ -138,16 +134,17 @@ impl IntRange { ty: Ty<'tcx>, end: RangeEnd, ) -> IntRange { - // Perform a shift if the underlying types are signed, - // which makes the interval arithmetic simpler. + // Perform a shift if the underlying types are signed, which makes the interval arithmetic + // type-independent. let bias = IntRange::signed_bias(tcx, ty); let (lo, hi) = (lo ^ bias, hi ^ bias); let offset = (end == RangeEnd::Excluded) as u128; - if lo > hi || (lo == hi && end == RangeEnd::Excluded) { + let hi = hi - offset; + if lo > hi { // This should have been caught earlier by E0030. - bug!("malformed range pattern: {}..={}", lo, (hi - offset)); + bug!("malformed range pattern: {lo}..={hi}"); } - IntRange { range: lo..=(hi - offset) } + IntRange { lo, hi } } // The return value of `signed_bias` should be XORed with an endpoint to encode/decode it. @@ -162,14 +159,12 @@ impl IntRange { } fn is_subrange(&self, other: &Self) -> bool { - other.range.start() <= self.range.start() && self.range.end() <= other.range.end() + other.lo <= self.lo && self.hi <= other.hi } fn intersection(&self, other: &Self) -> Option { - let (lo, hi) = self.boundaries(); - let (other_lo, other_hi) = other.boundaries(); - if lo <= other_hi && other_lo <= hi { - Some(IntRange { range: max(lo, other_lo)..=min(hi, other_hi) }) + if self.lo <= other.hi && other.lo <= self.hi { + Some(IntRange { lo: max(self.lo, other.lo), hi: min(self.hi, other.hi) }) } else { None } @@ -216,9 +211,8 @@ impl IntRange { fn unpack_intrange(range: IntRange) -> [IntBoundary; 2] { use IntBoundary::*; - let (lo, hi) = range.boundaries(); - let lo = JustBefore(lo); - let hi = match hi.checked_add(1) { + let lo = JustBefore(range.lo); + let hi = match range.hi.checked_add(1) { Some(m) => JustBefore(m), None => AfterMax, }; @@ -264,21 +258,19 @@ impl IntRange { use IntBoundary::*; use Presence::*; let presence = if paren_count > 0 { Seen } else { Unseen }; - let range = match (prev_bdy, bdy) { - (JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1), - (JustBefore(n), AfterMax) => n..=u128::MAX, + let (lo, hi) = match (prev_bdy, bdy) { + (JustBefore(n), JustBefore(m)) if n < m => (n, m - 1), + (JustBefore(n), AfterMax) => (n, u128::MAX), _ => unreachable!(), // Ruled out by the sorting and filtering we did }; - (presence, IntRange { range }) + (presence, IntRange { lo, hi }) }) } /// Only used for displaying the range. pub(super) fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> { - let (lo, hi) = self.boundaries(); - let bias = IntRange::signed_bias(tcx, ty); - let (lo_bits, hi_bits) = (lo ^ bias, hi ^ bias); + let (lo_bits, hi_bits) = (self.lo ^ bias, self.hi ^ bias); let env = ty::ParamEnv::empty().and(ty); let lo_const = mir::Const::from_bits(tcx, lo_bits, env); @@ -303,7 +295,7 @@ impl IntRange { /// first. impl fmt::Debug for IntRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let (lo, hi) = self.boundaries(); + let (lo, hi) = (self.lo, self.hi); write!(f, "{lo}")?; write!(f, "{}", RangeEnd::Included)?; write!(f, "{hi}") diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 25e0f3ceaa473..4b314535c893c 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -1031,9 +1031,10 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>( let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range()); for overlap_range in split_int_ranges.clone() { if overlap_range.is_singleton() { - let overlap: u128 = overlap_range.boundaries().0; - // Spans of ranges that start or end with the overlap. + let overlap: u128 = overlap_range.lo; + // Ranges that look like `lo..=overlap`. let mut prefixes: SmallVec<[_; 1]> = Default::default(); + // Ranges that look like `overlap..=hi`. let mut suffixes: SmallVec<[_; 1]> = Default::default(); // Iterate on patterns that contained `overlap`. for pat in column.iter() { @@ -1043,17 +1044,16 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>( // Don't lint when one of the ranges is a singleton. continue; } - let (start, end) = this_range.boundaries(); - if start == overlap { - // `this_range` looks like `overlap..=end`; it overlaps with any ranges that - // look like `start..=overlap`. + if this_range.lo == overlap { + // `this_range` looks like `overlap..=this_range.hi`; it overlaps with any + // ranges that look like `lo..=overlap`. if !prefixes.is_empty() { emit_lint(overlap_range, this_span, &prefixes); } suffixes.push(this_span) - } else if end == overlap { - // `this_range` looks like `start..=overlap`; it overlaps with any ranges - // that look like `overlap..=end`. + } else if this_range.hi == overlap { + // `this_range` looks like `this_range.lo..=overlap`; it overlaps with any + // ranges that look like `overlap..=hi`. if !suffixes.is_empty() { emit_lint(overlap_range, this_span, &suffixes); } From 6f35ae6f9bc32f591262c6eee94452e3eb2918d5 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 12 Oct 2023 19:47:33 +0200 Subject: [PATCH 6/9] Propagate half-open ranges through exhaustiveness checking --- .../src/thir/pattern/deconstruct_pat.rs | 253 +++++++++++------- .../src/thir/pattern/usefulness.rs | 7 +- 2 files changed, 158 insertions(+), 102 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 249273c3595f5..00a8bd6877389 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -63,6 +63,7 @@ use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT}; use self::Constructor::*; +use self::MaybeInfiniteInt::*; use self::SliceKind::*; use super::usefulness::{MatchCheckCtxt, PatCtxt}; @@ -91,20 +92,99 @@ enum Presence { Seen, } +/// A possibly infinite integer. Values are encoded such that the ordering on `u128` matches the +/// natural order on the original type. For example, `-128i8` is encoded as `0` and `127i8` as +/// `255`. See `signed_bias` for details. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum MaybeInfiniteInt { + NegInfinity, + /// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite`. + Finite(u128), + /// The integer after `u128::MAX`. Used when we switch to exclusive ranges in `IntRange::split`. + JustAfterMax, + PosInfinity, +} + +impl MaybeInfiniteInt { + // The return value of `signed_bias` should be XORed with a value to encode/decode it. + fn signed_bias(tcx: TyCtxt<'_>, ty: Ty<'_>) -> u128 { + match *ty.kind() { + ty::Int(ity) => { + let bits = Integer::from_int_ty(&tcx, ity).size().bits() as u128; + 1u128 << (bits - 1) + } + _ => 0, + } + } + + fn new_finite(tcx: TyCtxt<'_>, ty: Ty<'_>, bits: u128) -> Self { + let bias = Self::signed_bias(tcx, ty); + // Perform a shift if the underlying types are signed, which makes the interval arithmetic + // type-independent. + let x = bits ^ bias; + Finite(x) + } + fn from_pat_range_bdy<'tcx>( + bdy: PatRangeBoundary<'tcx>, + ty: Ty<'tcx>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Self { + match bdy { + PatRangeBoundary::NegInfinity => NegInfinity, + PatRangeBoundary::Finite(value) => { + let bits = value.eval_bits(tcx, param_env); + Self::new_finite(tcx, ty, bits) + } + PatRangeBoundary::PosInfinity => PosInfinity, + } + } + 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) + } + JustAfterMax | PosInfinity => PatRangeBoundary::PosInfinity, + } + } + + fn minus_one(self) -> Self { + match self { + Finite(n) => match n.checked_sub(1) { + Some(m) => Finite(m), + None => NegInfinity, + }, + JustAfterMax => Finite(u128::MAX), + x => x, + } + } + fn plus_one(self) -> Self { + match self { + Finite(n) => match n.checked_add(1) { + Some(m) => Finite(m), + None => JustAfterMax, + }, + x => x, + } + } +} + /// An inclusive interval, used for precise integer exhaustiveness checking. -/// `IntRange`s always store a contiguous range. This means that values are -/// encoded such that `0` encodes the minimum value for the integer, -/// regardless of the signedness. -/// For example, the pattern `-128..=127i8` is encoded as `0..=255`. -/// This makes comparisons and arithmetic on interval endpoints much more -/// straightforward. See `signed_bias` for details. +/// `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`. +/// +/// The range can have open ends. #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) struct IntRange { - pub(crate) lo: u128, - pub(crate) hi: u128, + pub(crate) lo: MaybeInfiniteInt, // Must not be `PosInfinity`. + pub(crate) hi: MaybeInfiniteInt, // Must not be `NegInfinity`. } impl IntRange { @@ -113,51 +193,31 @@ impl IntRange { matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_)) } + /// Best effort; will not know that e.g. `255u8..` is a singleton. pub(super) fn is_singleton(&self) -> bool { + // Since `lo` and `hi` can't be the same `Infinity`, this correctly only detects a + // `Finite(x)` singleton. self.lo == self.hi } #[inline] fn from_bits<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, bits: u128) -> IntRange { - let bias = IntRange::signed_bias(tcx, ty); - // Perform a shift if the underlying types are signed, which makes the interval arithmetic - // type-independent. - let val = bits ^ bias; - IntRange { lo: val, hi: val } + let x = MaybeInfiniteInt::new_finite(tcx, ty, bits); + IntRange { lo: x, hi: x } } #[inline] - fn from_range<'tcx>( - tcx: TyCtxt<'tcx>, - lo: u128, - hi: u128, - ty: Ty<'tcx>, - end: RangeEnd, - ) -> IntRange { - // Perform a shift if the underlying types are signed, which makes the interval arithmetic - // type-independent. - let bias = IntRange::signed_bias(tcx, ty); - let (lo, hi) = (lo ^ bias, hi ^ bias); - let offset = (end == RangeEnd::Excluded) as u128; - let hi = hi - offset; + fn from_range(lo: MaybeInfiniteInt, mut hi: MaybeInfiniteInt, end: RangeEnd) -> IntRange { + if end == RangeEnd::Excluded { + hi = hi.minus_one(); + } if lo > hi { // This should have been caught earlier by E0030. - bug!("malformed range pattern: {lo}..={hi}"); + bug!("malformed range pattern: {lo:?}..={hi:?}"); } IntRange { lo, hi } } - // The return value of `signed_bias` should be XORed with an endpoint to encode/decode it. - fn signed_bias(tcx: TyCtxt<'_>, ty: Ty<'_>) -> u128 { - match *ty.kind() { - ty::Int(ity) => { - let bits = Integer::from_int_ty(&tcx, ity).size().bits() as u128; - 1u128 << (bits - 1) - } - _ => 0, - } - } - fn is_subrange(&self, other: &Self) -> bool { other.lo <= self.lo && self.hi <= other.hi } @@ -201,29 +261,16 @@ impl IntRange { &self, column_ranges: impl Iterator, ) -> impl Iterator { - /// Represents a boundary between 2 integers. Because the intervals spanning boundaries must be - /// able to cover every integer, we need to be able to represent 2^128 + 1 such boundaries. - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - enum IntBoundary { - JustBefore(u128), - AfterMax, - } - - fn unpack_intrange(range: IntRange) -> [IntBoundary; 2] { - use IntBoundary::*; - let lo = JustBefore(range.lo); - let hi = match range.hi.checked_add(1) { - Some(m) => JustBefore(m), - None => AfterMax, - }; - [lo, hi] + // Make the range into an exclusive range. + fn unpack_intrange(range: IntRange) -> [MaybeInfiniteInt; 2] { + [range.lo, range.hi.plus_one()] } // The boundaries of ranges in `column_ranges` intersected with `self`. // We do parenthesis matching for input ranges. A boundary counts as +1 if it starts // a range and -1 if it ends it. When the count is > 0 between two boundaries, we // are within an input range. - let mut boundaries: Vec<(IntBoundary, isize)> = column_ranges + let mut boundaries: Vec<(MaybeInfiniteInt, isize)> = column_ranges .filter_map(|r| self.intersection(&r)) .map(unpack_intrange) .flat_map(|[lo, hi]| [(lo, 1), (hi, -1)]) @@ -233,7 +280,7 @@ impl IntRange { // the accumulated count between distinct boundary values. boundaries.sort_unstable(); - let [self_start, self_end] = unpack_intrange(self.clone()); + let [self_start, self_end] = unpack_intrange(*self); // Accumulate parenthesis counts. let mut paren_counter = 0isize; // Gather pairs of adjacent boundaries. @@ -255,36 +302,26 @@ impl IntRange { .filter(|&(prev_bdy, _, bdy)| prev_bdy != bdy) // Convert back to ranges. .map(move |(prev_bdy, paren_count, bdy)| { - use IntBoundary::*; use Presence::*; let presence = if paren_count > 0 { Seen } else { Unseen }; - let (lo, hi) = match (prev_bdy, bdy) { - (JustBefore(n), JustBefore(m)) if n < m => (n, m - 1), - (JustBefore(n), AfterMax) => (n, u128::MAX), - _ => unreachable!(), // Ruled out by the sorting and filtering we did - }; - (presence, IntRange { lo, hi }) + // Turn back into an inclusive range. + let range = IntRange::from_range(prev_bdy, bdy, RangeEnd::Excluded); + (presence, range) }) } /// Only used for displaying the range. - pub(super) fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> { - let bias = IntRange::signed_bias(tcx, ty); - let (lo_bits, hi_bits) = (self.lo ^ bias, self.hi ^ bias); - - let env = ty::ParamEnv::empty().and(ty); - let lo_const = mir::Const::from_bits(tcx, lo_bits, env); - let hi_const = mir::Const::from_bits(tcx, hi_bits, env); - - let kind = if lo_bits == hi_bits { - PatKind::Constant { value: lo_const } + pub(super) 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 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: PatRangeBoundary::Finite(lo_const), - hi: PatRangeBoundary::Finite(hi_const), - end: RangeEnd::Included, - ty, - })) + PatKind::Range(Box::new(PatRange { lo, hi, end: RangeEnd::Included, ty })) }; Pat { ty, span: DUMMY_SP, kind } @@ -295,10 +332,14 @@ impl IntRange { /// first. impl fmt::Debug for IntRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let (lo, hi) = (self.lo, self.hi); - write!(f, "{lo}")?; + if let Finite(lo) = self.lo { + write!(f, "{lo}")?; + } write!(f, "{}", RangeEnd::Included)?; - write!(f, "{hi}") + if let Finite(hi) = self.hi { + write!(f, "{hi}")?; + } + Ok(()) } } @@ -840,8 +881,13 @@ pub(super) struct SplitConstructorSet<'tcx> { impl ConstructorSet { #[instrument(level = "debug", skip(cx), ret)] pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> Self { - let make_range = - |start, end| IntRange::from_range(cx.tcx, start, end, ty, RangeEnd::Included); + let make_range = |start, end| { + IntRange::from_range( + MaybeInfiniteInt::new_finite(cx.tcx, ty, start), + MaybeInfiniteInt::new_finite(cx.tcx, ty, end), + RangeEnd::Included, + ) + }; // This determines the set of all possible constructors for the type `ty`. For numbers, // arrays and slices we use ranges and variable-length slices when appropriate. // @@ -1419,24 +1465,33 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { } } PatKind::Range(box PatRange { lo, hi, end, .. }) => { - use rustc_apfloat::Float; let ty = pat.ty; - // FIXME: handle half-open ranges - let lo = lo.eval_bits(ty, cx.tcx, cx.param_env); - let hi = hi.eval_bits(ty, cx.tcx, cx.param_env); ctor = match ty.kind() { ty::Char | ty::Int(_) | ty::Uint(_) => { - IntRange(IntRange::from_range(cx.tcx, lo, hi, ty, *end)) - } - ty::Float(ty::FloatTy::F32) => { - let lo = rustc_apfloat::ieee::Single::from_bits(lo); - let hi = rustc_apfloat::ieee::Single::from_bits(hi); - F32Range(lo, hi, *end) + let lo = + MaybeInfiniteInt::from_pat_range_bdy(*lo, ty, cx.tcx, cx.param_env); + let hi = + MaybeInfiniteInt::from_pat_range_bdy(*hi, ty, cx.tcx, cx.param_env); + IntRange(IntRange::from_range(lo, hi, *end)) } - ty::Float(ty::FloatTy::F64) => { - let lo = rustc_apfloat::ieee::Double::from_bits(lo); - let hi = rustc_apfloat::ieee::Double::from_bits(hi); - F64Range(lo, hi, *end) + ty::Float(fty) => { + use rustc_apfloat::Float; + let lo = lo.as_finite().map(|c| c.eval_bits(cx.tcx, cx.param_env)); + let hi = hi.as_finite().map(|c| c.eval_bits(cx.tcx, cx.param_env)); + match fty { + ty::FloatTy::F32 => { + use rustc_apfloat::ieee::Single; + let lo = lo.map(Single::from_bits).unwrap_or(-Single::INFINITY); + let hi = hi.map(Single::from_bits).unwrap_or(Single::INFINITY); + F32Range(lo, hi, *end) + } + ty::FloatTy::F64 => { + use rustc_apfloat::ieee::Double; + let lo = lo.map(Double::from_bits).unwrap_or(-Double::INFINITY); + let hi = hi.map(Double::from_bits).unwrap_or(Double::INFINITY); + F64Range(lo, hi, *end) + } + } } _ => bug!("invalid type for range pattern: {}", ty), }; @@ -1706,7 +1761,7 @@ impl<'tcx> WitnessPat<'tcx> { let mut subpatterns = self.iter_fields().map(|p| Box::new(p.to_pat(cx))); let kind = match &self.ctor { Bool(b) => PatKind::Constant { value: mir::Const::from_bool(cx.tcx, *b) }, - IntRange(range) => return range.to_pat(cx.tcx, self.ty), + IntRange(range) => return range.to_pat(self.ty, cx.tcx), Single | Variant(_) => match self.ty.kind() { ty::Tuple(..) => PatKind::Leaf { subpatterns: subpatterns diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 4b314535c893c..3a210f2587ece 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -308,7 +308,8 @@ use self::ArmType::*; use self::Usefulness::*; use super::deconstruct_pat::{ - Constructor, ConstructorSet, DeconstructedPat, IntRange, SplitConstructorSet, WitnessPat, + Constructor, ConstructorSet, DeconstructedPat, IntRange, MaybeInfiniteInt, SplitConstructorSet, + WitnessPat, }; use crate::errors::{NonExhaustiveOmittedPattern, Overlap, OverlappingRangeEndpoints, Uncovered}; @@ -1013,7 +1014,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>( if IntRange::is_integral(ty) { let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| { - let overlap_as_pat = overlap.to_pat(cx.tcx, ty); + let overlap_as_pat = overlap.to_pat(ty, cx.tcx); let overlaps: Vec<_> = overlapped_spans .iter() .copied() @@ -1031,7 +1032,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>( let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range()); for overlap_range in split_int_ranges.clone() { if overlap_range.is_singleton() { - let overlap: u128 = overlap_range.lo; + let overlap: MaybeInfiniteInt = overlap_range.lo; // Ranges that look like `lo..=overlap`. let mut prefixes: SmallVec<[_; 1]> = Default::default(); // Ranges that look like `overlap..=hi`. From a4875ae1e250895a00356efc2bd5e1cf8a609802 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 13 Oct 2023 00:20:06 +0200 Subject: [PATCH 7/9] Match usize/isize exhaustively --- .../src/thir/pattern/check_match.rs | 27 ++- .../src/thir/pattern/deconstruct_pat.rs | 136 ++++++++---- ...ture-gate-precise_pointer_size_matching.rs | 10 +- ...-gate-precise_pointer_size_matching.stderr | 18 +- .../pointer-sized-int.allow.stderr | 2 +- .../pointer-sized-int.deny.stderr | 194 +++++++----------- .../integer-ranges/pointer-sized-int.rs | 4 - .../precise_pointer_matching-message.rs | 10 +- .../precise_pointer_matching-message.stderr | 18 +- ...2-types-containing-non-exhaustive-types.rs | 16 +- ...pes-containing-non-exhaustive-types.stderr | 68 +++--- .../non-exhaustive-pattern-witness.rs | 53 +++-- .../non-exhaustive-pattern-witness.stderr | 50 ++--- .../usefulness/refutable-pattern-errors.rs | 4 +- .../refutable-pattern-errors.stderr | 4 +- .../usefulness/refutable-pattern-in-fn-arg.rs | 2 +- .../refutable-pattern-in-fn-arg.stderr | 2 +- .../tuple-struct-nonexhaustive.stderr | 10 +- 18 files changed, 321 insertions(+), 307 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index 9156af3425aea..f26f80105ee05 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -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>, ) { 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. @@ -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 \ diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 00a8bd6877389..5e9179896f791 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -56,6 +56,7 @@ use rustc_hir::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}; @@ -139,20 +140,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) { @@ -169,22 +182,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 { - pub(crate) lo: MaybeInfiniteInt, // Must not be `PosInfinity`. - pub(crate) hi: MaybeInfiniteInt, // Must not be `NegInfinity`. + pub(crate) lo: MaybeInfiniteInt, + pub(crate) hi: MaybeInfiniteInt, } impl IntRange { @@ -195,9 +210,7 @@ impl IntRange { /// Best effort; will not know that e.g. `255u8..` is a singleton. pub(super) fn is_singleton(&self) -> bool { - // Since `lo` and `hi` can't be the same `Infinity`, this correctly only detects a - // `Finite(x)` singleton. - self.lo == self.hi + self.lo == self.hi && self.lo.is_finite() } #[inline] @@ -310,18 +323,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. pub(super) 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 } @@ -843,9 +887,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, non_exhaustive: bool }, + Integers { range_1: IntRange, range_2: Option }, /// The type is matched by slices. The usize is the compile-time length of the array, if known. Slice(Option), /// The type is matched by slices whose elements are uninhabited. @@ -903,27 +945,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; @@ -1078,7 +1130,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()) { @@ -1095,10 +1147,6 @@ impl ConstructorSet { } } } - - if *non_exhaustive { - missing.push(NonExhaustive); - } } &ConstructorSet::Slice(array_len) => { let seen_slices = seen.map(|c| c.as_slice().unwrap()); diff --git a/tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.rs b/tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.rs index 4c77180b767a3..b4dc1fd45564d 100644 --- a/tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.rs +++ b/tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.rs @@ -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 => {} } } diff --git a/tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.stderr b/tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.stderr index 853b57052ace0..8694924e52fe0 100644 --- a/tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.stderr +++ b/tests/ui/feature-gates/feature-gate-precise_pointer_size_matching.stderr @@ -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 diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr index 12a928bf01803..7f26c93aa28a0 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr @@ -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 {} | ^^^^^^ diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr index 7fa9eb8eda664..d16ec5412db16 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr @@ -1,218 +1,162 @@ -error[E0004]: non-exhaustive patterns: `_` not covered +error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered --> $DIR/pointer-sized-int.rs:14: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/pointer-sized-int.rs:19: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[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:24:8 - | -LL | m!(0usize, 0..); - | ^^^^^^ pattern `_` 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 - = 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 | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ - -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:26:8 +error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered + --> $DIR/pointer-sized-int.rs:25:8 | LL | m!(0usize, 0..=usize::MAX); - | ^^^^^^ 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 | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ +LL | match $s { $($t)+ => {}, usize::MAX.. => todo!() } + | +++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:28:8 +error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered + --> $DIR/pointer-sized-int.rs:27:8 | LL | m!(0usize, 0..5 | 5..=usize::MAX); - | ^^^^^^ 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 | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ +LL | match $s { $($t)+ => {}, usize::MAX.. => todo!() } + | +++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:30:8 +error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered + --> $DIR/pointer-sized-int.rs:29:8 | LL | m!(0usize, 0..usize::MAX | usize::MAX); - | ^^^^^^ 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 | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ +LL | match $s { $($t)+ => {}, usize::MAX.. => todo!() } + | +++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `(_, _)` not covered - --> $DIR/pointer-sized-int.rs:32:8 +error[E0004]: non-exhaustive patterns: `(usize::MAX.., _)` not covered + --> $DIR/pointer-sized-int.rs:31:8 | LL | m!((0usize, true), (0..5, true) | (5..=usize::MAX, true) | (0..=usize::MAX, false)); - | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered + | ^^^^^^^^^^^^^^ pattern `(usize::MAX.., _)` not covered | = note: the matched value is of type `(usize, bool)` - = 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 | match $s { $($t)+ => {}, (_, _) => todo!() } - | +++++++++++++++++++ - -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:34:8 - | -LL | m!(0usize, 0..=usize::MAX | usize::MAX..); - | ^^^^^^ pattern `_` 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 - = 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 | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ - -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:37:8 - | -LL | m!(0isize, ..0 | 0..); - | ^^^^^^ pattern `_` 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 - = 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 - | -LL | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ +LL | match $s { $($t)+ => {}, (usize::MAX.., _) => todo!() } + | ++++++++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:39:8 +error[E0004]: non-exhaustive patterns: `..isize::MIN` and `isize::MAX..` not covered + --> $DIR/pointer-sized-int.rs:36:8 | LL | m!(0isize, isize::MIN..=isize::MAX); - | ^^^^^^ 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 | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ +LL | match $s { $($t)+ => {}, ..isize::MIN | isize::MAX.. => todo!() } + | ++++++++++++++++++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:41:8 +error[E0004]: non-exhaustive patterns: `..isize::MIN` and `isize::MAX..` not covered + --> $DIR/pointer-sized-int.rs:38:8 | LL | m!(0isize, isize::MIN..5 | 5..=isize::MAX); - | ^^^^^^ 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 | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ +LL | match $s { $($t)+ => {}, ..isize::MIN | isize::MAX.. => todo!() } + | ++++++++++++++++++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:43:8 +error[E0004]: non-exhaustive patterns: `..isize::MIN` and `isize::MAX..` not covered + --> $DIR/pointer-sized-int.rs:40:8 | LL | m!(0isize, isize::MIN..isize::MAX | isize::MAX); - | ^^^^^^ 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 | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ +LL | match $s { $($t)+ => {}, ..isize::MIN | isize::MAX.. => todo!() } + | ++++++++++++++++++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `(_, _)` not covered - --> $DIR/pointer-sized-int.rs:45:8 +error[E0004]: non-exhaustive patterns: `(..isize::MIN, _)` and `(isize::MAX.., _)` not covered + --> $DIR/pointer-sized-int.rs:42:8 | LL | m!((0isize, true), (isize::MIN..5, true) - | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered + | ^^^^^^^^^^^^^^ patterns `(..isize::MIN, _)` and `(isize::MAX.., _)` not covered | = note: the matched value is of type `(isize, bool)` - = 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 | match $s { $($t)+ => {}, (_, _) => todo!() } - | +++++++++++++++++++ +LL | match $s { $($t)+ => {}, (..isize::MIN, _) | (isize::MAX.., _) => todo!() } + | ++++++++++++++++++++++++++++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:48:8 - | -LL | m!(0isize, ..=isize::MIN | isize::MIN..=isize::MAX | isize::MAX..); - | ^^^^^^ pattern `_` 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 - = 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 - | -LL | match $s { $($t)+ => {}, _ => todo!() } - | ++++++++++++++ - -error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:51:11 +error[E0004]: non-exhaustive patterns: `..isize::MIN` and `isize::MAX..` not covered + --> $DIR/pointer-sized-int.rs:47: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 ~ 1 ..= isize::MAX => {}, -LL + _ => todo!() +LL + ..isize::MIN | isize::MAX.. => todo!() | 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 {} | ^^^^^^ @@ -225,6 +169,6 @@ LL + _ => todo!(), LL + } | -error: aborting due to 16 previous errors +error: aborting due to 12 previous errors For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs index b55f8f3e51683..20a3cbe127f42 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs @@ -22,7 +22,6 @@ fn main() { } m!(0usize, 0..); - //[deny]~^ ERROR non-exhaustive patterns m!(0usize, 0..=usize::MAX); //[deny]~^ ERROR non-exhaustive patterns m!(0usize, 0..5 | 5..=usize::MAX); @@ -32,10 +31,8 @@ fn main() { m!((0usize, true), (0..5, true) | (5..=usize::MAX, true) | (0..=usize::MAX, false)); //[deny]~^ ERROR non-exhaustive patterns m!(0usize, 0..=usize::MAX | usize::MAX..); - //[deny]~^ ERROR non-exhaustive patterns m!(0isize, ..0 | 0..); - //[deny]~^ ERROR non-exhaustive patterns m!(0isize, isize::MIN..=isize::MAX); //[deny]~^ ERROR non-exhaustive patterns m!(0isize, isize::MIN..5 | 5..=isize::MAX); @@ -46,7 +43,6 @@ fn main() { | (5..=isize::MAX, true) | (isize::MIN..=isize::MAX, false)); //[deny]~^^ ERROR non-exhaustive patterns m!(0isize, ..=isize::MIN | isize::MIN..=isize::MAX | isize::MAX..); - //[deny]~^ ERROR non-exhaustive patterns match 0isize { //[deny]~^ ERROR non-exhaustive patterns diff --git a/tests/ui/pattern/usefulness/integer-ranges/precise_pointer_matching-message.rs b/tests/ui/pattern/usefulness/integer-ranges/precise_pointer_matching-message.rs index a2aa655ca5413..d60f479c0d155 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/precise_pointer_matching-message.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/precise_pointer_matching-message.rs @@ -1,18 +1,18 @@ // This tests that the lint message explains the reason for the error. 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 => {} } } diff --git a/tests/ui/pattern/usefulness/integer-ranges/precise_pointer_matching-message.stderr b/tests/ui/pattern/usefulness/integer-ranges/precise_pointer_matching-message.stderr index b80411b26b049..a7f93648ed3dd 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/precise_pointer_matching-message.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/precise_pointer_matching-message.stderr @@ -1,31 +1,31 @@ -error[E0004]: non-exhaustive patterns: `_` not covered +error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered --> $DIR/precise_pointer_matching-message.rs:3: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/precise_pointer_matching-message.rs:11: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 diff --git a/tests/ui/pattern/usefulness/issue-85222-types-containing-non-exhaustive-types.rs b/tests/ui/pattern/usefulness/issue-85222-types-containing-non-exhaustive-types.rs index 8f58227ee2c64..6cbcfed709f9c 100644 --- a/tests/ui/pattern/usefulness/issue-85222-types-containing-non-exhaustive-types.rs +++ b/tests/ui/pattern/usefulness/issue-85222-types-containing-non-exhaustive-types.rs @@ -6,19 +6,19 @@ struct B(T, U); fn main() { match 0 { - //~^ ERROR non-exhaustive patterns: `_` not covered [E0004] + //~^ ERROR non-exhaustive patterns: `usize::MAX..` not covered [E0004] 0 => (), 1..=usize::MAX => (), } match (0usize, 0usize) { - //~^ ERROR non-exhaustive patterns: `(_, _)` not covered [E0004] + //~^ ERROR non-exhaustive patterns: `(usize::MAX.., _)` not covered [E0004] (0, 0) => (), (1..=usize::MAX, 1..=usize::MAX) => (), } match (0isize, 0usize) { - //~^ ERROR non-exhaustive patterns: `(_, _)` not covered [E0004] + //~^ ERROR non-exhaustive patterns: `(..isize::MIN, _)` and `(isize::MAX.., _)` not covered [E0004] (isize::MIN..=isize::MAX, 0) => (), (isize::MIN..=isize::MAX, 1..=usize::MAX) => (), } @@ -30,14 +30,14 @@ fn main() { } match Some(4) { - //~^ ERROR non-exhaustive patterns: `Some(_)` not covered + //~^ ERROR non-exhaustive patterns: `Some(usize::MAX..)` not covered Some(0) => (), Some(1..=usize::MAX) => (), None => (), } match Some(Some(Some(0))) { - //~^ ERROR non-exhaustive patterns: `Some(Some(Some(_)))` not covered + //~^ ERROR non-exhaustive patterns: `Some(Some(Some(usize::MAX..)))` not covered Some(Some(Some(0))) => (), Some(Some(Some(1..=usize::MAX))) => (), Some(Some(None)) => (), @@ -46,13 +46,13 @@ fn main() { } match (A { a: 0usize }) { - //~^ ERROR non-exhaustive patterns: `A { .. }` not covered [E0004] + //~^ ERROR non-exhaustive patterns: `A { a: usize::MAX.. }` not covered [E0004] A { a: 0 } => (), A { a: 1..=usize::MAX } => (), } match B(0isize, 0usize) { - //~^ ERROR non-exhaustive patterns: `B(_, _)` not covered [E0004] + //~^ ERROR non-exhaustive patterns: `B(..isize::MIN, _)` and `B(isize::MAX.., _)` not covered [E0004] B(isize::MIN..=isize::MAX, 0) => (), B(isize::MIN..=isize::MAX, 1..=usize::MAX) => (), } @@ -60,7 +60,7 @@ fn main() { // Should report only the note about usize not having fixed max value and not report // report the note about isize match B(0isize, 0usize) { - //~^ ERROR non-exhaustive patterns: `B(_, _)` not covered [E0004] + //~^ ERROR non-exhaustive patterns: `B(_, usize::MAX..)` not covered [E0004] B(_, 0) => (), B(_, 1..=usize::MAX) => (), } diff --git a/tests/ui/pattern/usefulness/issue-85222-types-containing-non-exhaustive-types.stderr b/tests/ui/pattern/usefulness/issue-85222-types-containing-non-exhaustive-types.stderr index ea1d99e20ae1f..556efcda516ab 100644 --- a/tests/ui/pattern/usefulness/issue-85222-types-containing-non-exhaustive-types.stderr +++ b/tests/ui/pattern/usefulness/issue-85222-types-containing-non-exhaustive-types.stderr @@ -1,46 +1,46 @@ -error[E0004]: non-exhaustive patterns: `_` not covered +error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:8:11 | LL | match 0 { - | ^ 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 ~ 1..=usize::MAX => (), -LL ~ _ => todo!(), +LL ~ usize::MAX.. => todo!(), | -error[E0004]: non-exhaustive patterns: `(_, _)` not covered +error[E0004]: non-exhaustive patterns: `(usize::MAX.., _)` not covered --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:14:11 | LL | match (0usize, 0usize) { - | ^^^^^^^^^^^^^^^^ pattern `(_, _)` not covered + | ^^^^^^^^^^^^^^^^ pattern `(usize::MAX.., _)` not covered | = note: the matched value is of type `(usize, 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 ~ (1..=usize::MAX, 1..=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/issue-85222-types-containing-non-exhaustive-types.rs:20:11 | LL | match (0isize, 0usize) { - | ^^^^^^^^^^^^^^^^ pattern `(_, _)` not covered + | ^^^^^^^^^^^^^^^^ patterns `(..isize::MIN, _)` and `(isize::MAX.., _)` not covered | = note: the matched value is of type `(isize, usize)` - = 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, 1..=usize::MAX) => (), -LL ~ (_, _) => todo!(), +LL ~ (..isize::MIN, _) | (isize::MAX.., _) => todo!(), | error[E0004]: non-exhaustive patterns: `Some(_)` not covered @@ -61,11 +61,11 @@ LL ~ None => {}, LL + Some(_) => todo!() | -error[E0004]: non-exhaustive patterns: `Some(_)` not covered +error[E0004]: non-exhaustive patterns: `Some(usize::MAX..)` not covered --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:32:11 | LL | match Some(4) { - | ^^^^^^^ pattern `Some(_)` not covered + | ^^^^^^^ pattern `Some(usize::MAX..)` not covered | note: `Option` defined here --> $SRC_DIR/core/src/option.rs:LL:COL @@ -73,19 +73,19 @@ note: `Option` defined here | = note: not covered = note: the matched value is of type `Option` - = 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 ~ None => (), -LL ~ Some(_) => todo!(), +LL ~ Some(usize::MAX..) => todo!(), | -error[E0004]: non-exhaustive patterns: `Some(Some(Some(_)))` not covered +error[E0004]: non-exhaustive patterns: `Some(Some(Some(usize::MAX..)))` not covered --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:39:11 | LL | match Some(Some(Some(0))) { - | ^^^^^^^^^^^^^^^^^^^ pattern `Some(Some(Some(_)))` not covered + | ^^^^^^^^^^^^^^^^^^^ pattern `Some(Some(Some(usize::MAX..)))` not covered | note: `Option>>` defined here --> $SRC_DIR/core/src/option.rs:LL:COL @@ -97,19 +97,19 @@ note: `Option>>` defined here | = note: not covered = note: the matched value is of type `Option>>` - = 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 ~ None => (), -LL ~ Some(Some(Some(_))) => todo!(), +LL ~ Some(Some(Some(usize::MAX..))) => todo!(), | -error[E0004]: non-exhaustive patterns: `A { .. }` not covered +error[E0004]: non-exhaustive patterns: `A { a: usize::MAX.. }` not covered --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:48:11 | LL | match (A { a: 0usize }) { - | ^^^^^^^^^^^^^^^^^ pattern `A { .. }` not covered + | ^^^^^^^^^^^^^^^^^ pattern `A { a: usize::MAX.. }` not covered | note: `A` defined here --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:1:8 @@ -117,19 +117,19 @@ note: `A` defined here LL | struct A { | ^ = note: the matched value is of type `A` - = 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 ~ A { a: 1..=usize::MAX } => (), -LL ~ A { .. } => todo!(), +LL ~ A { a: usize::MAX.. } => todo!(), | -error[E0004]: non-exhaustive patterns: `B(_, _)` not covered +error[E0004]: non-exhaustive patterns: `B(..isize::MIN, _)` and `B(isize::MAX.., _)` not covered --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:54:11 | LL | match B(0isize, 0usize) { - | ^^^^^^^^^^^^^^^^^ pattern `B(_, _)` not covered + | ^^^^^^^^^^^^^^^^^ patterns `B(..isize::MIN, _)` and `B(isize::MAX.., _)` not covered | note: `B` defined here --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:5:8 @@ -137,19 +137,19 @@ note: `B` defined here LL | struct B(T, U); | ^ = note: the matched value is of type `B` - = 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 ~ B(isize::MIN..=isize::MAX, 1..=usize::MAX) => (), -LL ~ B(_, _) => todo!(), +LL ~ B(..isize::MIN, _) | B(isize::MAX.., _) => todo!(), | -error[E0004]: non-exhaustive patterns: `B(_, _)` not covered +error[E0004]: non-exhaustive patterns: `B(_, usize::MAX..)` not covered --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:62:11 | LL | match B(0isize, 0usize) { - | ^^^^^^^^^^^^^^^^^ pattern `B(_, _)` not covered + | ^^^^^^^^^^^^^^^^^ pattern `B(_, usize::MAX..)` not covered | note: `B` defined here --> $DIR/issue-85222-types-containing-non-exhaustive-types.rs:5:8 @@ -157,12 +157,12 @@ note: `B` defined here LL | struct B(T, U); | ^ = note: the matched value is of type `B` - = 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 ~ B(_, 1..=usize::MAX) => (), -LL ~ B(_, _) => todo!(), +LL ~ B(_, usize::MAX..) => todo!(), | error: aborting due to 9 previous errors diff --git a/tests/ui/pattern/usefulness/non-exhaustive-pattern-witness.rs b/tests/ui/pattern/usefulness/non-exhaustive-pattern-witness.rs index 4bd34421922bd..9e60d4f41a1f7 100644 --- a/tests/ui/pattern/usefulness/non-exhaustive-pattern-witness.rs +++ b/tests/ui/pattern/usefulness/non-exhaustive-pattern-witness.rs @@ -1,88 +1,101 @@ struct Foo { first: bool, - second: Option<[usize; 4]> + second: Option<[usize; 4]>, } fn struct_with_a_nested_enum_and_vector() { match (Foo { first: true, second: None }) { -//~^ ERROR non-exhaustive patterns: `Foo { first: false, second: Some([_, _, _, _]) }` not covered + //~^ ERROR non-exhaustive patterns: `Foo { first: false, second: Some([0_usize, _, _, _]) }` and `Foo { first: false, second: Some([2_usize.., _, _, _]) }` not covered Foo { first: true, second: None } => (), Foo { first: true, second: Some(_) } => (), Foo { first: false, second: None } => (), - Foo { first: false, second: Some([1, 2, 3, 4]) } => () + Foo { first: false, second: Some([1, 2, 3, 4]) } => (), } } enum Color { Red, Green, - CustomRGBA { a: bool, r: u8, g: u8, b: u8 } + CustomRGBA { a: bool, r: u8, g: u8, b: u8 }, } fn enum_with_single_missing_variant() { match Color::Red { - //~^ ERROR non-exhaustive patterns: `Color::Red` not covered + //~^ ERROR non-exhaustive patterns: `Color::Red` not covered Color::CustomRGBA { .. } => (), - Color::Green => () + Color::Green => (), } } enum Direction { - North, East, South, West + North, + East, + South, + West, } fn enum_with_multiple_missing_variants() { match Direction::North { - //~^ ERROR non-exhaustive patterns: `Direction::East`, `Direction::South` and `Direction::West` not covered - Direction::North => () + //~^ ERROR non-exhaustive patterns: `Direction::East`, `Direction::South` and `Direction::West` not covered + Direction::North => (), } } enum ExcessiveEnum { - First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth, Eleventh, Twelfth + First, + Second, + Third, + Fourth, + Fifth, + Sixth, + Seventh, + Eighth, + Ninth, + Tenth, + Eleventh, + Twelfth, } fn enum_with_excessive_missing_variants() { match ExcessiveEnum::First { - //~^ ERROR `ExcessiveEnum::Second`, `ExcessiveEnum::Third`, `ExcessiveEnum::Fourth` and 8 more not covered - - ExcessiveEnum::First => () + //~^ ERROR `ExcessiveEnum::Second`, `ExcessiveEnum::Third`, `ExcessiveEnum::Fourth` and 8 more not covered + ExcessiveEnum::First => (), } } fn enum_struct_variant() { match Color::Red { - //~^ ERROR non-exhaustive patterns: `Color::CustomRGBA { a: true, .. }` not covered + //~^ ERROR non-exhaustive patterns: `Color::CustomRGBA { a: true, .. }` not covered Color::Red => (), Color::Green => (), Color::CustomRGBA { a: false, r: _, g: _, b: 0 } => (), - Color::CustomRGBA { a: false, r: _, g: _, b: _ } => () + Color::CustomRGBA { a: false, r: _, g: _, b: _ } => (), } } enum Enum { First, - Second(bool) + Second(bool), } fn vectors_with_nested_enums() { let x: &'static [Enum] = &[Enum::First, Enum::Second(false)]; match *x { - //~^ ERROR non-exhaustive patterns: `[Enum::Second(true), Enum::Second(false)]` not covered + //~^ ERROR non-exhaustive patterns: `[Enum::Second(true), Enum::Second(false)]` not covered [] => (), [_] => (), [Enum::First, _] => (), [Enum::Second(true), Enum::First] => (), [Enum::Second(true), Enum::Second(true)] => (), [Enum::Second(false), _] => (), - [_, _, ref tail @ .., _] => () + [_, _, ref tail @ .., _] => (), } } fn missing_nil() { match ((), false) { - //~^ ERROR non-exhaustive patterns: `((), false)` not covered - ((), true) => () + //~^ ERROR non-exhaustive patterns: `((), false)` not covered + ((), true) => (), } } diff --git a/tests/ui/pattern/usefulness/non-exhaustive-pattern-witness.stderr b/tests/ui/pattern/usefulness/non-exhaustive-pattern-witness.stderr index d798ec722ddd2..f914b98d9231f 100644 --- a/tests/ui/pattern/usefulness/non-exhaustive-pattern-witness.stderr +++ b/tests/ui/pattern/usefulness/non-exhaustive-pattern-witness.stderr @@ -1,8 +1,8 @@ -error[E0004]: non-exhaustive patterns: `Foo { first: false, second: Some([_, _, _, _]) }` not covered +error[E0004]: non-exhaustive patterns: `Foo { first: false, second: Some([0_usize, _, _, _]) }` and `Foo { first: false, second: Some([2_usize.., _, _, _]) }` not covered --> $DIR/non-exhaustive-pattern-witness.rs:7:11 | LL | match (Foo { first: true, second: None }) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `Foo { first: false, second: Some([_, _, _, _]) }` not covered + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ patterns `Foo { first: false, second: Some([0_usize, _, _, _]) }` and `Foo { first: false, second: Some([2_usize.., _, _, _]) }` not covered | note: `Foo` defined here --> $DIR/non-exhaustive-pattern-witness.rs:1:8 @@ -10,12 +10,10 @@ note: `Foo` defined here LL | struct Foo { | ^^^ = note: the matched value is of type `Foo` - = note: `usize` does not have a fixed maximum value, so a wildcard `_` is 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 +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 ~ Foo { first: false, second: Some([1, 2, 3, 4]) } => (), -LL + Foo { first: false, second: Some([_, _, _, _]) } => todo!() +LL ~ Foo { first: false, second: Some([0_usize, _, _, _]) } | Foo { first: false, second: Some([2_usize.., _, _, _]) } => todo!(), | error[E0004]: non-exhaustive patterns: `Color::Red` not covered @@ -35,40 +33,42 @@ LL | Red, 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 ~ Color::Green => (), -LL + Color::Red => todo!() +LL ~ Color::Red => todo!(), | error[E0004]: non-exhaustive patterns: `Direction::East`, `Direction::South` and `Direction::West` not covered - --> $DIR/non-exhaustive-pattern-witness.rs:35:11 + --> $DIR/non-exhaustive-pattern-witness.rs:38:11 | LL | match Direction::North { | ^^^^^^^^^^^^^^^^ patterns `Direction::East`, `Direction::South` and `Direction::West` not covered | note: `Direction` defined here - --> $DIR/non-exhaustive-pattern-witness.rs:31:12 + --> $DIR/non-exhaustive-pattern-witness.rs:32:5 | LL | enum Direction { | --------- -LL | North, East, South, West - | ^^^^ ^^^^^ ^^^^ not covered - | | | - | | not covered - | not covered +LL | North, +LL | East, + | ^^^^ not covered +LL | South, + | ^^^^^ not covered +LL | West, + | ^^^^ not covered = note: the matched value is of type `Direction` 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 ~ Direction::North => (), -LL + Direction::East | Direction::South | Direction::West => todo!() +LL ~ Direction::East | Direction::South | Direction::West => todo!(), | error[E0004]: non-exhaustive patterns: `ExcessiveEnum::Second`, `ExcessiveEnum::Third`, `ExcessiveEnum::Fourth` and 8 more not covered - --> $DIR/non-exhaustive-pattern-witness.rs:46:11 + --> $DIR/non-exhaustive-pattern-witness.rs:60:11 | LL | match ExcessiveEnum::First { | ^^^^^^^^^^^^^^^^^^^^ patterns `ExcessiveEnum::Second`, `ExcessiveEnum::Third`, `ExcessiveEnum::Fourth` and 8 more not covered | note: `ExcessiveEnum` defined here - --> $DIR/non-exhaustive-pattern-witness.rs:41:6 + --> $DIR/non-exhaustive-pattern-witness.rs:44:6 | LL | enum ExcessiveEnum { | ^^^^^^^^^^^^^ @@ -76,11 +76,11 @@ LL | enum ExcessiveEnum { help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms | LL ~ ExcessiveEnum::First => (), -LL + _ => todo!() +LL ~ _ => todo!(), | error[E0004]: non-exhaustive patterns: `Color::CustomRGBA { a: true, .. }` not covered - --> $DIR/non-exhaustive-pattern-witness.rs:54:11 + --> $DIR/non-exhaustive-pattern-witness.rs:67:11 | LL | match Color::Red { | ^^^^^^^^^^ pattern `Color::CustomRGBA { a: true, .. }` not covered @@ -91,17 +91,17 @@ note: `Color` defined here LL | enum Color { | ----- ... -LL | CustomRGBA { a: bool, r: u8, g: u8, b: u8 } +LL | CustomRGBA { a: bool, r: u8, g: u8, b: u8 }, | ^^^^^^^^^^ not covered = note: the matched value is of type `Color` 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 ~ Color::CustomRGBA { a: false, r: _, g: _, b: _ } => (), -LL + Color::CustomRGBA { a: true, .. } => todo!() +LL ~ Color::CustomRGBA { a: true, .. } => todo!(), | error[E0004]: non-exhaustive patterns: `[Enum::Second(true), Enum::Second(false)]` not covered - --> $DIR/non-exhaustive-pattern-witness.rs:70:11 + --> $DIR/non-exhaustive-pattern-witness.rs:83:11 | LL | match *x { | ^^ pattern `[Enum::Second(true), Enum::Second(false)]` not covered @@ -110,11 +110,11 @@ LL | match *x { 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 ~ [_, _, ref tail @ .., _] => (), -LL + [Enum::Second(true), Enum::Second(false)] => todo!() +LL ~ [Enum::Second(true), Enum::Second(false)] => todo!(), | error[E0004]: non-exhaustive patterns: `((), false)` not covered - --> $DIR/non-exhaustive-pattern-witness.rs:83:11 + --> $DIR/non-exhaustive-pattern-witness.rs:96:11 | LL | match ((), false) { | ^^^^^^^^^^^ pattern `((), false)` not covered @@ -123,7 +123,7 @@ LL | match ((), false) { 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 ~ ((), true) => (), -LL + ((), false) => todo!() +LL ~ ((), false) => todo!(), | error: aborting due to 7 previous errors diff --git a/tests/ui/pattern/usefulness/refutable-pattern-errors.rs b/tests/ui/pattern/usefulness/refutable-pattern-errors.rs index 7a3e991d59317..7603da1bb2ce8 100644 --- a/tests/ui/pattern/usefulness/refutable-pattern-errors.rs +++ b/tests/ui/pattern/usefulness/refutable-pattern-errors.rs @@ -1,6 +1,6 @@ -fn func((1, (Some(1), 2..=3)): (isize, (Option, isize))) { } +fn func((1, (Some(1), 2..=3)): (isize, (Option, isize))) {} //~^ ERROR refutable pattern in function argument -//~| `(_, _)` not covered +//~| `(..=0_isize, _)` and `(2_isize.., _)` not covered fn main() { let (1, (Some(1), 2..=3)) = (1, (None, 2)); diff --git a/tests/ui/pattern/usefulness/refutable-pattern-errors.stderr b/tests/ui/pattern/usefulness/refutable-pattern-errors.stderr index beb51a4d45043..e66cd11302387 100644 --- a/tests/ui/pattern/usefulness/refutable-pattern-errors.stderr +++ b/tests/ui/pattern/usefulness/refutable-pattern-errors.stderr @@ -1,8 +1,8 @@ error[E0005]: refutable pattern in function argument --> $DIR/refutable-pattern-errors.rs:1:9 | -LL | fn func((1, (Some(1), 2..=3)): (isize, (Option, isize))) { } - | ^^^^^^^^^^^^^^^^^^^^^ pattern `(_, _)` not covered +LL | fn func((1, (Some(1), 2..=3)): (isize, (Option, isize))) {} + | ^^^^^^^^^^^^^^^^^^^^^ patterns `(..=0_isize, _)` and `(2_isize.., _)` not covered | = note: the matched value is of type `(isize, (Option, isize))` diff --git a/tests/ui/pattern/usefulness/refutable-pattern-in-fn-arg.rs b/tests/ui/pattern/usefulness/refutable-pattern-in-fn-arg.rs index 17dc38ab25d92..4203dd94d43aa 100644 --- a/tests/ui/pattern/usefulness/refutable-pattern-in-fn-arg.rs +++ b/tests/ui/pattern/usefulness/refutable-pattern-in-fn-arg.rs @@ -1,6 +1,6 @@ fn main() { let f = |3: isize| println!("hello"); //~^ ERROR refutable pattern in function argument - //~| `_` not covered + //~| `..=2_isize` and `4_isize..` not covered f(4); } diff --git a/tests/ui/pattern/usefulness/refutable-pattern-in-fn-arg.stderr b/tests/ui/pattern/usefulness/refutable-pattern-in-fn-arg.stderr index ab3f6f69fb161..01f077909e8f7 100644 --- a/tests/ui/pattern/usefulness/refutable-pattern-in-fn-arg.stderr +++ b/tests/ui/pattern/usefulness/refutable-pattern-in-fn-arg.stderr @@ -2,7 +2,7 @@ error[E0005]: refutable pattern in function argument --> $DIR/refutable-pattern-in-fn-arg.rs:2:14 | LL | let f = |3: isize| println!("hello"); - | ^ pattern `_` not covered + | ^ patterns `..=2_isize` and `4_isize..` not covered | = note: the matched value is of type `isize` help: alternatively, you could prepend the pattern with an underscore to define a new named variable; identifiers cannot begin with digits diff --git a/tests/ui/pattern/usefulness/tuple-struct-nonexhaustive.stderr b/tests/ui/pattern/usefulness/tuple-struct-nonexhaustive.stderr index 50c7fc889f421..ef707ed4aa40f 100644 --- a/tests/ui/pattern/usefulness/tuple-struct-nonexhaustive.stderr +++ b/tests/ui/pattern/usefulness/tuple-struct-nonexhaustive.stderr @@ -1,8 +1,8 @@ -error[E0004]: non-exhaustive patterns: `Foo(_, _)` not covered +error[E0004]: non-exhaustive patterns: `Foo(..=0_isize, _)` and `Foo(3_isize.., _)` not covered --> $DIR/tuple-struct-nonexhaustive.rs:5:11 | LL | match x { - | ^ pattern `Foo(_, _)` not covered + | ^ patterns `Foo(..=0_isize, _)` and `Foo(3_isize.., _)` not covered | note: `Foo` defined here --> $DIR/tuple-struct-nonexhaustive.rs:1:8 @@ -10,12 +10,10 @@ note: `Foo` defined here LL | struct Foo(isize, isize); | ^^^ = note: the matched value is of type `Foo` - = note: `isize` does not have a fixed maximum value, so a wildcard `_` is 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 ~ Foo(2, b) => println!("{}", b), -LL + Foo(_, _) => todo!() +LL + Foo(..=0_isize, _) | Foo(3_isize.., _) => todo!() | error: aborting due to previous error From feb769a5c9e3954a4dd0a83d321b2996a1ac5667 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sat, 21 Oct 2023 20:16:10 +0200 Subject: [PATCH 8/9] s/to_pat/to_diagnostic_pat/ --- compiler/rustc_mir_build/src/errors.rs | 12 +++++-- .../src/thir/pattern/check_match.rs | 8 ++--- .../src/thir/pattern/deconstruct_pat.rs | 32 +++++++++++-------- .../src/thir/pattern/usefulness.rs | 2 +- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 730670a8369cc..5bfce3ab51008 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -807,13 +807,19 @@ impl<'tcx> Uncovered<'tcx> { cx: &MatchCheckCtxt<'p, 'tcx>, witnesses: Vec>, ) -> Self { - let witness_1 = witnesses.get(0).unwrap().to_pat(cx); + let witness_1 = witnesses.get(0).unwrap().to_diagnostic_pat(cx); Self { span, count: witnesses.len(), // Substitute dummy values if witnesses is smaller than 3. These will never be read. - witness_2: witnesses.get(1).map(|w| w.to_pat(cx)).unwrap_or_else(|| witness_1.clone()), - witness_3: witnesses.get(2).map(|w| w.to_pat(cx)).unwrap_or_else(|| witness_1.clone()), + witness_2: witnesses + .get(1) + .map(|w| w.to_diagnostic_pat(cx)) + .unwrap_or_else(|| witness_1.clone()), + witness_3: witnesses + .get(2) + .map(|w| w.to_diagnostic_pat(cx)) + .unwrap_or_else(|| witness_1.clone()), witness_1, remainder: witnesses.len().saturating_sub(3), } diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index f26f80105ee05..933653e708e94 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -760,7 +760,7 @@ fn non_exhaustive_match<'p, 'tcx>( pattern = if witnesses.len() < 4 { witnesses .iter() - .map(|witness| witness.to_pat(cx).to_string()) + .map(|witness| witness.to_diagnostic_pat(cx).to_string()) .collect::>() .join(" | ") } else { @@ -915,13 +915,13 @@ pub(crate) fn joined_uncovered_patterns<'p, 'tcx>( witnesses: &[WitnessPat<'tcx>], ) -> String { const LIMIT: usize = 3; - let pat_to_str = |pat: &WitnessPat<'tcx>| pat.to_pat(cx).to_string(); + let pat_to_str = |pat: &WitnessPat<'tcx>| pat.to_diagnostic_pat(cx).to_string(); match witnesses { [] => bug!(), - [witness] => format!("`{}`", witness.to_pat(cx)), + [witness] => format!("`{}`", witness.to_diagnostic_pat(cx)), [head @ .., tail] if head.len() < LIMIT => { let head: Vec<_> = head.iter().map(pat_to_str).collect(); - format!("`{}` and `{}`", head.join("`, `"), tail.to_pat(cx)) + format!("`{}` and `{}`", head.join("`, `"), tail.to_diagnostic_pat(cx)) } _ => { let (head, tail) = witnesses.split_at(LIMIT); diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 5e9179896f791..8d9998d17ed5b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -140,8 +140,13 @@ 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> { + /// Used only for diagnostics. + /// This could change from finite to infinite if we got `usize::MAX+1` after range splitting. + fn to_diagnostic_pat_range_bdy<'tcx>( + self, + ty: Ty<'tcx>, + tcx: TyCtxt<'tcx>, + ) -> PatRangeBoundary<'tcx> { match self { NegInfinity => PatRangeBoundary::NegInfinity, Finite(x) => { @@ -326,25 +331,25 @@ 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`. + // First check if we are usize/isize to avoid unnecessary `to_diagnostic_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); + let lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx); + let hi = self.hi.to_diagnostic_pat_range_bdy(ty, tcx); matches!(lo, PatRangeBoundary::PosInfinity) || matches!(hi, PatRangeBoundary::NegInfinity) } } /// Only used for displaying the range. - pub(super) fn to_pat<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Pat<'tcx> { + pub(super) fn to_diagnostic_pat<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Pat<'tcx> { 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 lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx); let value = lo.as_finite().unwrap(); PatKind::Constant { value } } else { - let mut lo = self.lo.to_pat_range_bdy(ty, tcx); - let mut hi = self.hi.to_pat_range_bdy(ty, tcx); + let mut lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx); + let mut hi = self.hi.to_diagnostic_pat_range_bdy(ty, tcx); let end = if hi.is_finite() { RangeEnd::Included } else { @@ -1803,13 +1808,14 @@ impl<'tcx> WitnessPat<'tcx> { self.ty } - /// Convert back to a `thir::Pat` for diagnostic purposes. - pub(crate) fn to_pat(&self, cx: &MatchCheckCtxt<'_, 'tcx>) -> Pat<'tcx> { + /// Convert back to a `thir::Pat` for diagnostic purposes. This panics for patterns that don't + /// appear in diagnostics, like float ranges. + pub(crate) fn to_diagnostic_pat(&self, cx: &MatchCheckCtxt<'_, 'tcx>) -> Pat<'tcx> { let is_wildcard = |pat: &Pat<'_>| matches!(pat.kind, PatKind::Wild); - let mut subpatterns = self.iter_fields().map(|p| Box::new(p.to_pat(cx))); + let mut subpatterns = self.iter_fields().map(|p| Box::new(p.to_diagnostic_pat(cx))); let kind = match &self.ctor { Bool(b) => PatKind::Constant { value: mir::Const::from_bool(cx.tcx, *b) }, - IntRange(range) => return range.to_pat(self.ty, cx.tcx), + IntRange(range) => return range.to_diagnostic_pat(self.ty, cx.tcx), Single | Variant(_) => match self.ty.kind() { ty::Tuple(..) => PatKind::Leaf { subpatterns: subpatterns diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 3a210f2587ece..c0920a3ef5a5b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -1014,7 +1014,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>( if IntRange::is_integral(ty) { let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| { - let overlap_as_pat = overlap.to_pat(ty, cx.tcx); + let overlap_as_pat = overlap.to_diagnostic_pat(ty, cx.tcx); let overlaps: Vec<_> = overlapped_spans .iter() .copied() From 35fe75d8f3800a77cf23a6d7b23d9c28311c5dfb Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sat, 21 Oct 2023 20:16:48 +0200 Subject: [PATCH 9/9] Make IntRange exclusive --- .../src/thir/pattern/deconstruct_pat.rs | 120 ++++++++++-------- .../src/thir/pattern/usefulness.rs | 2 +- 2 files changed, 65 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 8d9998d17ed5b..0c7c2c6f9b4b7 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -101,7 +101,7 @@ pub(crate) enum MaybeInfiniteInt { NegInfinity, /// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite`. Finite(u128), - /// The integer after `u128::MAX`. Used when we switch to exclusive ranges in `IntRange::split`. + /// The integer after `u128::MAX`. We need it to represent `x..=u128::MAX` as an exclusive range. JustAfterMax, PosInfinity, } @@ -140,8 +140,11 @@ impl MaybeInfiniteInt { PatRangeBoundary::PosInfinity => PosInfinity, } } + /// Used only for diagnostics. - /// This could change from finite to infinite if we got `usize::MAX+1` after range splitting. + /// Note: it is possible to get `isize/usize::MAX+1` here, as explained in the doc for + /// [`IntRange::split`]. This cannot be represented as a `Const`, so we represent it with + /// `PosInfinity`. fn to_diagnostic_pat_range_bdy<'tcx>( self, ty: Ty<'tcx>, @@ -168,20 +171,19 @@ impl MaybeInfiniteInt { } } - fn is_finite(self) -> bool { - matches!(self, Finite(_)) - } - fn minus_one(self) -> Self { + /// Note: this will not turn a finite value into an infinite one or vice-versa. + pub(crate) fn minus_one(self) -> Self { match self { Finite(n) => match n.checked_sub(1) { Some(m) => Finite(m), - None => NegInfinity, + None => bug!(), }, JustAfterMax => Finite(u128::MAX), x => x, } } - fn plus_one(self) -> Self { + /// Note: this will not turn a finite value into an infinite one or vice-versa. + pub(crate) fn plus_one(self) -> Self { match self { Finite(n) => match n.checked_add(1) { Some(m) => Finite(m), @@ -193,18 +195,15 @@ impl MaybeInfiniteInt { } } -/// An inclusive interval, used for precise integer exhaustiveness checking. `IntRange`s always +/// An exclusive 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`. -/// -/// Note: the range can be `NegInfinity..=NegInfinity` or `PosInfinity..=PosInfinity` to represent -/// the values before `isize::MIN` and after `isize::MAX`/`usize::MAX`. +/// space: i.e., `range.lo < range.hi`. #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) struct IntRange { - pub(crate) lo: MaybeInfiniteInt, - pub(crate) hi: MaybeInfiniteInt, + pub(crate) lo: MaybeInfiniteInt, // Must not be `PosInfinity`. + pub(crate) hi: MaybeInfiniteInt, // Must not be `NegInfinity`. } impl IntRange { @@ -215,23 +214,25 @@ impl IntRange { /// Best effort; will not know that e.g. `255u8..` is a singleton. pub(super) fn is_singleton(&self) -> bool { - self.lo == self.hi && self.lo.is_finite() + // Since `lo` and `hi` can't be the same `Infinity` and `plus_one` never changes from finite + // to infinite, this correctly only detects ranges that contain exacly one `Finite(x)`. + self.lo.plus_one() == self.hi } #[inline] fn from_bits<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, bits: u128) -> IntRange { let x = MaybeInfiniteInt::new_finite(tcx, ty, bits); - IntRange { lo: x, hi: x } + IntRange { lo: x, hi: x.plus_one() } } #[inline] fn from_range(lo: MaybeInfiniteInt, mut hi: MaybeInfiniteInt, end: RangeEnd) -> IntRange { - if end == RangeEnd::Excluded { - hi = hi.minus_one(); + if end == RangeEnd::Included { + hi = hi.plus_one(); } - if lo > hi { + if lo >= hi { // This should have been caught earlier by E0030. - bug!("malformed range pattern: {lo:?}..={hi:?}"); + bug!("malformed range pattern: {lo:?}..{hi:?}"); } IntRange { lo, hi } } @@ -241,7 +242,7 @@ impl IntRange { } fn intersection(&self, other: &Self) -> Option { - if self.lo <= other.hi && other.lo <= self.hi { + if self.lo < other.hi && other.lo < self.hi { Some(IntRange { lo: max(self.lo, other.lo), hi: min(self.hi, other.hi) }) } else { None @@ -275,38 +276,45 @@ impl IntRange { /// ``` /// where each sequence of dashes is an output range, and dashes outside parentheses are marked /// as `Presence::Missing`. + /// + /// ## `isize`/`usize` + /// + /// Whereas a wildcard of type `i32` stands for the range `i32::MIN..=i32::MAX`, a `usize` + /// wildcard stands for `0..PosInfinity` and a `isize` wildcard stands for + /// `NegInfinity..PosInfinity`. In other words, as far as `IntRange` is concerned, there are + /// values before `isize::MIN` and after `usize::MAX`/`isize::MAX`. + /// This is to avoid e.g. `0..(u32::MAX as usize)` from being exhaustive on one architecture and + /// not others. See discussions around the `precise_pointer_size_matching` feature for more + /// details. + /// + /// These infinities affect splitting subtly: it is possible to get `NegInfinity..0` and + /// `usize::MAX+1..PosInfinity` in the output. Diagnostics must be careful to handle these + /// fictitious ranges sensibly. fn split( &self, column_ranges: impl Iterator, ) -> impl Iterator { - // Make the range into an exclusive range. - fn unpack_intrange(range: IntRange) -> [MaybeInfiniteInt; 2] { - [range.lo, range.hi.plus_one()] - } - // The boundaries of ranges in `column_ranges` intersected with `self`. // We do parenthesis matching for input ranges. A boundary counts as +1 if it starts // a range and -1 if it ends it. When the count is > 0 between two boundaries, we // are within an input range. let mut boundaries: Vec<(MaybeInfiniteInt, isize)> = column_ranges .filter_map(|r| self.intersection(&r)) - .map(unpack_intrange) - .flat_map(|[lo, hi]| [(lo, 1), (hi, -1)]) + .flat_map(|r| [(r.lo, 1), (r.hi, -1)]) .collect(); // We sort by boundary, and for each boundary we sort the "closing parentheses" first. The // order of +1/-1 for a same boundary value is actually irrelevant, because we only look at // the accumulated count between distinct boundary values. boundaries.sort_unstable(); - let [self_start, self_end] = unpack_intrange(*self); // Accumulate parenthesis counts. let mut paren_counter = 0isize; // Gather pairs of adjacent boundaries. - let mut prev_bdy = self_start; + let mut prev_bdy = self.lo; boundaries .into_iter() // End with the end of the range. The count is ignored. - .chain(once((self_end, 0))) + .chain(once((self.hi, 0))) // List pairs of adjacent boundaries and the count between them. .map(move |(bdy, delta)| { // `delta` affects the count as we cross `bdy`, so the relevant count between @@ -322,21 +330,22 @@ impl IntRange { .map(move |(prev_bdy, paren_count, bdy)| { use Presence::*; let presence = if paren_count > 0 { Seen } else { Unseen }; - // Turn back into an inclusive range. - let range = IntRange::from_range(prev_bdy, bdy, RangeEnd::Excluded); + let range = IntRange { lo: prev_bdy, hi: bdy }; (presence, range) }) } - /// Whether the range denotes the values before `isize::MIN` or the values after - /// `usize::MAX`/`isize::MAX`. + /// Whether the range denotes the fictitious values before `isize::MIN` or after + /// `usize::MAX`/`isize::MAX` (see doc of [`IntRange::split`] for why these exist). 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_diagnostic_pat_range_bdy`. ty.is_ptr_sized_integral() && !tcx.features().precise_pointer_size_matching && { + // The two invalid ranges are `NegInfinity..isize::MIN` (represented as + // `NegInfinity..0`), and `{u,i}size::MAX+1..PosInfinity`. `to_diagnostic_pat_range_bdy` + // converts `MAX+1` to `PosInfinity`, and we couldn't have `PosInfinity` in `self.lo` + // otherwise. let lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx); - let hi = self.hi.to_diagnostic_pat_range_bdy(ty, tcx); matches!(lo, PatRangeBoundary::PosInfinity) - || matches!(hi, PatRangeBoundary::NegInfinity) + || matches!(self.hi, MaybeInfiniteInt::Finite(0)) } } /// Only used for displaying the range. @@ -348,28 +357,27 @@ impl IntRange { let value = lo.as_finite().unwrap(); PatKind::Constant { value } } else { + // We convert to an inclusive range for diagnostics. + let mut end = RangeEnd::Included; let mut lo = self.lo.to_diagnostic_pat_range_bdy(ty, tcx); - let mut hi = self.hi.to_diagnostic_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. + // The only reason to get `PosInfinity` here is the special case where + // `to_diagnostic_pat_range_bdy` found `{u,i}size::MAX+1`. So the range denotes the + // fictitious values after `{u,i}size::MAX` (see [`IntRange::split`] for why we do + // this). We show this to the user 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); } + let hi = if matches!(self.hi, MaybeInfiniteInt::Finite(0)) { + // The range encodes `..ty::MIN`, so we can't convert it to an inclusive range. + end = RangeEnd::Excluded; + self.hi + } else { + self.hi.minus_one() + }; + let hi = hi.to_diagnostic_pat_range_bdy(ty, tcx); PatKind::Range(Box::new(PatRange { lo, hi, end, ty })) }; @@ -384,7 +392,7 @@ impl fmt::Debug for IntRange { if let Finite(lo) = self.lo { write!(f, "{lo}")?; } - write!(f, "{}", RangeEnd::Included)?; + write!(f, "{}", RangeEnd::Excluded)?; if let Finite(hi) = self.hi { write!(f, "{hi}")?; } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index c0920a3ef5a5b..27bf5b92552f7 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -1052,7 +1052,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>( emit_lint(overlap_range, this_span, &prefixes); } suffixes.push(this_span) - } else if this_range.hi == overlap { + } else if this_range.hi == overlap.plus_one() { // `this_range` looks like `this_range.lo..=overlap`; it overlaps with any // ranges that look like `overlap..=hi`. if !suffixes.is_empty() {