diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index 34cf9c5d0a5b2..ae8458c199503 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -43,6 +43,8 @@ mod num; #[unstable(feature = "convert_float_to_int", issue = "67057")] pub use num::FloatToInt; +#[unstable(feature = "integer_casts", issue = "157388")] +pub use num::{BoundedCastFromInt, CheckedCastFromInt}; /// The identity function. /// diff --git a/library/core/src/convert/num.rs b/library/core/src/convert/num.rs index 7179512e126ed..19398b4680583 100644 --- a/library/core/src/convert/num.rs +++ b/library/core/src/convert/num.rs @@ -630,3 +630,98 @@ impl_nonzero_int_try_from_nonzero_int!(i32 => u8, u16, u32, u64, u128, usize); impl_nonzero_int_try_from_nonzero_int!(i64 => u8, u16, u32, u64, u128, usize); impl_nonzero_int_try_from_nonzero_int!(i128 => u8, u16, u32, u64, u128, usize); impl_nonzero_int_try_from_nonzero_int!(isize => u8, u16, u32, u64, u128, usize); + +/// Conversion between integers, wrapping around or saturating at the target type's boundaries. +#[unstable(feature = "integer_casts", issue = "157388")] +#[rustc_const_unstable(feature = "integer_casts", issue = "157388")] +pub impl(self) const trait BoundedCastFromInt: Sized { + /// Converts `value` to this type, wrapping around at the boundary of the type. + #[unstable(feature = "integer_casts", issue = "157388")] + fn wrapping_cast_from(value: T) -> Self; + + /// Converts `value` to this type, saturating at the numeric bounds instead of overflowing. + #[unstable(feature = "integer_casts", issue = "157388")] + fn saturating_cast_from(value: T) -> Self; +} + +/// Fallible conversion between integers. +#[unstable(feature = "integer_casts", issue = "157388")] +#[rustc_const_unstable(feature = "integer_casts", issue = "157388")] +pub impl(self) const trait CheckedCastFromInt: Sized { + /// Converts `value` to this type, returning `None` if overflow would have occurred. + #[unstable(feature = "integer_casts", issue = "157388")] + fn checked_cast_from(value: T) -> Option; + + /// Converts `value` to this type, assuming overflow cannot occur. + /// + /// # Safety + /// + /// This results in undefined behavior when `value` will overflow when + /// converted to this type. + #[unstable(feature = "integer_casts", issue = "157388")] + unsafe fn unchecked_cast_from(value: T) -> Self; + + /// Converts `value` to this type, panicking on overflow. + /// + /// # Panics + /// + /// This function will always panic on overflow, regardless of whether overflow checks are enabled. + #[unstable(feature = "integer_casts", issue = "157388")] + fn strict_cast_from(value: T) -> Self; +} + +macro_rules! impl_int_cast { + ($Src:ty as [$($Dst:ty),*]) => {$( + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + impl const CheckedCastFromInt<$Src> for $Dst { + #[inline] + fn checked_cast_from(value: $Src) -> Option { + value.try_into().ok() + } + + #[inline(always)] + unsafe fn unchecked_cast_from(value: $Src) -> Self { + // SAFETY: the safety contract must be upheld by the caller. + unsafe { value.try_into().unwrap_unchecked() } + } + + #[inline] + #[track_caller] + fn strict_cast_from(value: $Src) -> Self { + match value.try_into() { + Ok(x) => x, + Err(_) => core::num::imp::overflow_panic::cast_integer() + } + } + } + + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + impl const BoundedCastFromInt<$Src> for $Dst { + #[inline(always)] + fn wrapping_cast_from(value: $Src) -> Self { + value as Self + } + + #[inline] + #[allow(unused_comparisons)] + #[allow(irrefutable_let_patterns)] + fn saturating_cast_from(value: $Src) -> Self { + if let Ok(x) = value.try_into() { + return x; + } + + if value < 0 { <$Dst>::MIN } else { <$Dst>::MAX } + } + } + )*}; +} + +macro_rules! impl_all_int_casts { + ([$($Src:ty),*]) => {$( + impl_int_cast!($Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]); + )*}; +} + +impl_all_int_casts!([u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]); diff --git a/library/core/src/num/imp/overflow_panic.rs b/library/core/src/num/imp/overflow_panic.rs index 53aee5e98eb1a..f62d09ea041ab 100644 --- a/library/core/src/num/imp/overflow_panic.rs +++ b/library/core/src/num/imp/overflow_panic.rs @@ -58,6 +58,6 @@ pub(in crate::num) const fn pow() -> ! { #[cold] #[track_caller] -pub(in crate::num) const fn cast_integer() -> ! { +pub(crate) const fn cast_integer() -> ! { panic!("attempt to cast integer with overflow") } diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index ba1a297664ffd..a833c367693a7 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -4042,5 +4042,123 @@ macro_rules! int_impl { { traits::WidenTarget::internal_widen(self) } + + + /// Converts `self` to the target integer type, saturating at the numeric + /// bounds instead of overflowing. + /// + /// # Examples + /// + /// ``` + /// #![feature(integer_casts)] + #[doc = concat!("assert_eq!(i8::MAX, ", stringify!($SelfT), "::MAX.saturating_cast());")] + #[doc = concat!("assert_eq!(i8::MIN, ", stringify!($SelfT), "::MIN.saturating_cast());")] + #[doc = concat!("assert_eq!(42u8, 42", stringify!($SelfT), ".saturating_cast());")] + #[doc = concat!("assert_eq!(0u8, (-42", stringify!($SelfT), ").saturating_cast());")] + /// ``` + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + pub const fn saturating_cast>(self) -> T { + T::saturating_cast_from(self) + } + + /// Converts `self` to the target integer type, wrapping around at the + /// boundary of the target type. + /// + /// # Examples + /// + /// ``` + /// #![feature(integer_casts)] + #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX as i8, ", stringify!($SelfT), "::MAX.wrapping_cast());")] + #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MIN as i8, ", stringify!($SelfT), "::MIN.wrapping_cast());")] + #[doc = concat!("assert_eq!(42u8, 42", stringify!($SelfT), ".wrapping_cast());")] + #[doc = concat!("assert_eq!(u8::MAX - 41, (-42", stringify!($SelfT), ").wrapping_cast());")] + /// ``` + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + pub const fn wrapping_cast>(self) -> T { + T::wrapping_cast_from(self) + } + + /// Converts `self` to the target integer type, returning `None` if the value + /// is not representable by the target type. + /// + /// # Examples + /// + /// ``` + /// #![feature(integer_casts)] + #[doc = concat!("assert_eq!(Some(42u8), 42", stringify!($SelfT), ".checked_cast());")] + #[doc = concat!("assert_eq!((-42", stringify!($SelfT), ").checked_cast::(), None);")] + /// ``` + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + pub const fn checked_cast>(self) -> Option { + T::checked_cast_from(self) + } + + /// Converts `self` to the target integer type, panicking if the value + /// is not representable by the target type. + /// + /// # Panics + /// + /// This function will panic if the value is not representable by the target type. + /// + /// # Examples + /// + /// ``` + /// #![feature(integer_casts)] + #[doc = concat!("assert_eq!(42u8, 42", stringify!($SelfT), ".strict_cast());")] + /// ``` + /// + /// The following will panic: + /// + /// ```should_panic + /// #![feature(integer_casts)] + #[doc = concat!("let _ = (-42", stringify!($SelfT), ").strict_cast::();")] + /// ``` + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + #[track_caller] + pub const fn strict_cast>(self) -> T { + T::strict_cast_from(self) + } + + /// Converts `self` to the target integer type, assuming the value is + /// representable by the target type. + /// + /// # Safety + /// + /// This results in undefined behavior if the integer value of `self` is bigger than `T::MAX`, + /// or smaller than `T::MIN`, where `T` is the target type. + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + pub const unsafe fn unchecked_cast>(self) -> T { + assert_unsafe_precondition!( + check_language_ub, + concat!(stringify!($SelfT), "::unchecked_cast must fit in the target type"), + ( + // Check has to be performed up-front because it depends on generic T. + in_bounds: bool = { + let cast_val = self.checked_cast::(); + let ret = cast_val.is_some(); + core::mem::forget(cast_val); // We don't have const Drop, but we know it's an int. + ret + }, + ) => in_bounds, + ); + + // SAFETY: this is guaranteed to be safe by the caller. + unsafe { T::unchecked_cast_from(self) } + } } } diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index ed91c1e6a4ff1..59c2b11470f4a 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -2,6 +2,7 @@ #![stable(feature = "rust1", since = "1.0.0")] +use crate::convert::{BoundedCastFromInt, CheckedCastFromInt}; use crate::panic::const_panic; use crate::str::FromStr; use crate::ub_checks::assert_unsafe_precondition; diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 2a82e4f4fc547..b48e39752ad02 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -4217,5 +4217,120 @@ macro_rules! uint_impl { { traits::WidenTarget::internal_widen(self) } + + /// Converts `self` to the target integer type, saturating at the numeric + /// bounds instead of overflowing. + /// + /// # Examples + /// + /// ``` + /// #![feature(integer_casts)] + #[doc = concat!("assert_eq!(255u8, ", stringify!($SelfT), "::MAX.saturating_cast());")] + #[doc = concat!("assert_eq!(127i8, ", stringify!($SelfT), "::MAX.saturating_cast());")] + #[doc = concat!("assert_eq!(42i8, 42", stringify!($SelfT), ".saturating_cast());")] + /// ``` + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + pub const fn saturating_cast>(self) -> T { + T::saturating_cast_from(self) + } + + /// Converts `self` to the target integer type, wrapping around at the + /// boundary of the target type. + /// + /// # Examples + /// + /// ``` + /// #![feature(integer_casts)] + #[doc = concat!("assert_eq!(255u8, ", stringify!($SelfT), "::MAX.wrapping_cast());")] + #[doc = concat!("assert_eq!(42i8, 42", stringify!($SelfT), ".wrapping_cast());")] + #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX as i8, ", stringify!($SelfT), "::MAX.wrapping_cast());")] + /// ``` + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + pub const fn wrapping_cast>(self) -> T { + T::wrapping_cast_from(self) + } + + /// Converts `self` to the target integer type, returning `None` if the value + /// is not representable by the target type. + /// + /// # Examples + /// + /// ``` + /// #![feature(integer_casts)] + #[doc = concat!("assert_eq!(Some(42u8), 42", stringify!($SelfT), ".checked_cast());")] + #[doc = concat!("assert_eq!(128", stringify!($SelfT), ".checked_cast::(), None);")] + /// ``` + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + pub const fn checked_cast>(self) -> Option { + T::checked_cast_from(self) + } + + /// Converts `self` to the target integer type, panicking if the value + /// is not representable by the target type. + /// + /// # Panics + /// + /// This function will panic if the value is not representable by the target type. + /// + /// # Examples + /// + /// ``` + /// #![feature(integer_casts)] + #[doc = concat!("assert_eq!(42u8, 42", stringify!($SelfT), ".strict_cast());")] + /// ``` + /// + /// The following will panic: + /// + /// ```should_panic + /// #![feature(integer_casts)] + #[doc = concat!("let _ = 128", stringify!($SelfT), ".strict_cast::();")] + /// ``` + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + #[track_caller] + pub const fn strict_cast>(self) -> T { + T::strict_cast_from(self) + } + + /// Converts `self` to the target integer type, assuming the value is + /// representable by the target type. + /// + /// # Safety + /// + /// This results in undefined behavior if the integer value of `self` is bigger than `T::MAX`, + /// or smaller than `T::MIN`, where `T` is the target type. + #[must_use = "this returns the cast result and does not modify the original"] + #[unstable(feature = "integer_casts", issue = "157388")] + #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] + #[inline(always)] + pub const unsafe fn unchecked_cast>(self) -> T { + assert_unsafe_precondition!( + check_language_ub, + concat!(stringify!($SelfT), "::unchecked_cast must fit in the target type"), + ( + // Check has to be performed up-front because it depends on generic T. + in_bounds: bool = { + let cast_val = self.checked_cast::(); + let ret = cast_val.is_some(); + core::mem::forget(cast_val); // We don't have const Drop, but we know it's an int. + ret + }, + ) => in_bounds, + ); + + // SAFETY: this is guaranteed to be safe by the caller. + unsafe { T::unchecked_cast_from(self) } + } } } diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index c30ed92b0e946..1799da9d4abc2 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -68,6 +68,7 @@ #![feature(hashmap_internals)] #![feature(int_from_ascii)] #![feature(int_roundings)] +#![feature(integer_casts)] #![feature(io_slice_as_bytes)] #![feature(ip)] #![feature(is_ascii_octdigit)] @@ -83,6 +84,7 @@ #![feature(iterator_try_collect)] #![feature(iterator_try_reduce)] #![feature(layout_for_ptr)] +#![feature(macro_metavar_expr_concat)] #![feature(maybe_uninit_fill)] #![feature(maybe_uninit_uninit_array_transpose)] #![feature(min_specialization)] diff --git a/library/coretests/tests/num/cast.rs b/library/coretests/tests/num/cast.rs new file mode 100644 index 0000000000000..dddfd3a02ee64 --- /dev/null +++ b/library/coretests/tests/num/cast.rs @@ -0,0 +1,101 @@ +use std::sync::LazyLock; + +// All (negative) integers which are at or near a power of two to test +// boundary conditions. We use strings so we can convert to any type using +// parsing, while still being able to use the *position* in ORDERED_VALS for +// comparisons. +static ORDERED_VALS: LazyLock> = LazyLock::new(|| { + let mut pos_int_vals = Vec::new(); + for exp in 0..=127 { + let val = 1_u128 << exp; + pos_int_vals.push(val.saturating_sub(2)); + pos_int_vals.push(val.saturating_sub(1)); + pos_int_vals.push(val); + pos_int_vals.push(val.saturating_add(1)); + pos_int_vals.push(val.saturating_add(2)); + } + pos_int_vals.sort(); + pos_int_vals.dedup(); + + let mut pos_str_vals: Vec<_> = pos_int_vals.iter().map(|i| i.to_string()).collect(); + + // These are manual because the upper ones overflow even u128. + pos_str_vals.push("340282366920938463463374607431768211454".to_owned()); // 2**128 - 2 + pos_str_vals.push("340282366920938463463374607431768211455".to_owned()); // 2**128 - 1 + pos_str_vals.push("340282366920938463463374607431768211456".to_owned()); // 2**128 + pos_str_vals.push("340282366920938463463374607431768211457".to_owned()); // 2**128 + 1 + pos_str_vals.push("340282366920938463463374607431768211458".to_owned()); // 2**128 + 2 + + let mut out = Vec::new(); + for val in pos_str_vals[1..].iter().rev() { + out.push(format!("-{val}")); + } + out.extend(pos_str_vals); + out +}); + +macro_rules! make_checked_cast_test { + ($Src:ident as [$($Dst:ident),*]) => {$( + #[test] + #[allow(non_snake_case)] + fn ${concat(test_checked_cast_, $Src, _to_, $Dst)}() { + for val in ORDERED_VALS.iter() { + if let Some(src) = val.parse::<$Src>().ok() { + let dst: Option<$Dst> = val.parse().ok(); + assert_eq!(src.checked_cast::<$Dst>(), dst); + } + } + } + )*} +} + +macro_rules! make_bounded_cast_test { + (|$src:ident| $raw:expr, $Src:ident as [$($Dst:ident),*]) => {$( + #[test] + #[allow(non_snake_case)] + fn ${concat(test_bounded_cast_, $Src, _to_, $Dst)}() { + let ord_idx = |s| ORDERED_VALS.iter().position(|v| *v == s).unwrap(); + let dst_min_idx = ord_idx(<$Dst>::MIN.to_string()); + let dst_max_idx = ord_idx(<$Dst>::MAX.to_string()); + for (val_idx, val) in ORDERED_VALS.iter().enumerate() { + if let Some($src) = val.parse::<$Src>().ok() { + let dst: Option<$Dst> = val.parse().ok(); + + assert_eq!($src.wrapping_cast::<$Dst>(), $raw as $Dst); + + if val_idx > dst_max_idx { + assert_eq!($src.saturating_cast::<$Dst>(), <$Dst>::MAX); + } else if val_idx < dst_min_idx { + assert_eq!($src.saturating_cast::<$Dst>(), <$Dst>::MIN); + } else { + assert_eq!($src.saturating_cast::<$Dst>(), dst.unwrap()); + } + } + } + } + )*} +} + +macro_rules! make_tests_for_src { + (|$src:ident| $raw:expr, [$($Src:ident),*]) => {$( + make_checked_cast_test!( $Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]); + make_bounded_cast_test!(|$src| $raw, $Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]); + + // NonZero types are not (yet) implemented. + // make_checked_cast_test!($Src as [ + // NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, + // NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize + // ]); + )*} +} + +make_tests_for_src!(|x| x, [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]); + +// NonZero types are not (yet) implemented. +// make_tests_for_src!( +// |x| x.get(), +// [ +// NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, +// NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize +// ] +// ); diff --git a/library/coretests/tests/num/mod.rs b/library/coretests/tests/num/mod.rs index a82ac6fcd45f0..90666fc0b1ad2 100644 --- a/library/coretests/tests/num/mod.rs +++ b/library/coretests/tests/num/mod.rs @@ -23,6 +23,7 @@ mod u8; mod bignum; mod carryless_mul; +mod cast; mod const_from; mod dec2flt; mod float_ieee754_flt2dec_dec2flt;