From ac3874453b3c90e16f6d37b5725d56c29e72c39d Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Thu, 4 Jun 2026 00:53:16 +0200 Subject: [PATCH 1/4] Implement feature `integer_casts` --- library/core/src/convert/mod.rs | 2 + library/core/src/convert/num.rs | 122 +++++++++++++++++++++ library/core/src/num/imp/overflow_panic.rs | 2 +- library/core/src/num/int_macros.rs | 117 ++++++++++++++++++++ library/core/src/num/mod.rs | 1 + library/core/src/num/uint_macros.rs | 114 +++++++++++++++++++ 6 files changed, 357 insertions(+), 1 deletion(-) 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..9f6b6453cea94 100644 --- a/library/core/src/convert/num.rs +++ b/library/core/src/convert/num.rs @@ -1,5 +1,32 @@ use crate::num::{IntErrorKind, TryFromIntError}; +mod private { + /// This trait being unreachable from outside the crate prevents other + /// implementations of the integer cast traits. + #[unstable(feature = "integer_casts", issue = "157388")] + pub trait Sealed {} + + /// This trait being unreachable from outside the crate prevents other + /// implementations of the integer cast traits. + /// + /// `Cast : SealedCast` avoids the orphan rule, which would otherwise + /// allow e.g. implementing `Cast` for `u8`. + #[unstable(feature = "integer_casts", issue = "157388")] + pub trait SealedCast: Sealed {} + + #[unstable(feature = "integer_casts", issue = "157388")] + impl SealedCast for U {} + + macro_rules! impl_sealed_int { + ([$($T:ty),*]) => {$( + #[unstable(feature = "integer_casts", issue = "157388")] + impl Sealed for $T { } + )*}; + } + + impl_sealed_int!([u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]); +} + /// Supporting trait for inherent methods of `f32` and `f64` such as `to_int_unchecked`. /// Typically doesn’t need to be used directly. #[unstable(feature = "convert_float_to_int", issue = "67057")] @@ -630,3 +657,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 const trait BoundedCastFromInt: private::SealedCast + 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 const trait CheckedCastFromInt: private::SealedCast + 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..51e1cc48420fa 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -4042,5 +4042,122 @@ 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 + /// does not lie in the target type's domain. + /// + /// # 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 + /// does not lie in the target type's domain. + /// + /// # Panics + /// + /// This function will panic if the value does not lie in the target type's domain. + /// + /// # 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 lies in the target type's domain. + /// + /// # 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..7419df618b7b5 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -4217,5 +4217,119 @@ 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 + /// does not lie in the target type's domain. + /// + /// # 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 + /// does not lie in the target type's domain. + /// + /// # Panics + /// + /// This function will panic if the value does not lie in the target type's domain. + /// + /// # 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 lies in the target type's domain. + /// + /// # 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) } + } } } From 537b09174217757845621446b48130eb61cd01f9 Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Sun, 7 Jun 2026 09:37:57 +0200 Subject: [PATCH 2/4] Use impl(self) instead of Sealed --- library/core/src/convert/num.rs | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/library/core/src/convert/num.rs b/library/core/src/convert/num.rs index 9f6b6453cea94..19398b4680583 100644 --- a/library/core/src/convert/num.rs +++ b/library/core/src/convert/num.rs @@ -1,32 +1,5 @@ use crate::num::{IntErrorKind, TryFromIntError}; -mod private { - /// This trait being unreachable from outside the crate prevents other - /// implementations of the integer cast traits. - #[unstable(feature = "integer_casts", issue = "157388")] - pub trait Sealed {} - - /// This trait being unreachable from outside the crate prevents other - /// implementations of the integer cast traits. - /// - /// `Cast : SealedCast` avoids the orphan rule, which would otherwise - /// allow e.g. implementing `Cast` for `u8`. - #[unstable(feature = "integer_casts", issue = "157388")] - pub trait SealedCast: Sealed {} - - #[unstable(feature = "integer_casts", issue = "157388")] - impl SealedCast for U {} - - macro_rules! impl_sealed_int { - ([$($T:ty),*]) => {$( - #[unstable(feature = "integer_casts", issue = "157388")] - impl Sealed for $T { } - )*}; - } - - impl_sealed_int!([u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]); -} - /// Supporting trait for inherent methods of `f32` and `f64` such as `to_int_unchecked`. /// Typically doesn’t need to be used directly. #[unstable(feature = "convert_float_to_int", issue = "67057")] @@ -661,7 +634,7 @@ 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 const trait BoundedCastFromInt: private::SealedCast + Sized { +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; @@ -674,7 +647,7 @@ pub const trait BoundedCastFromInt: private::SealedCast + Sized { /// Fallible conversion between integers. #[unstable(feature = "integer_casts", issue = "157388")] #[rustc_const_unstable(feature = "integer_casts", issue = "157388")] -pub const trait CheckedCastFromInt: private::SealedCast + Sized { +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; From e70c619a9d3d95f10a57a5cf77a56af73a7481d1 Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Sun, 7 Jun 2026 10:01:52 +0200 Subject: [PATCH 3/4] Use 'representable' rather than 'lies in domain' --- library/core/src/num/int_macros.rs | 9 +++++---- library/core/src/num/uint_macros.rs | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 51e1cc48420fa..a833c367693a7 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -4085,7 +4085,7 @@ macro_rules! int_impl { } /// Converts `self` to the target integer type, returning `None` if the value - /// does not lie in the target type's domain. + /// is not representable by the target type. /// /// # Examples /// @@ -4103,11 +4103,11 @@ macro_rules! int_impl { } /// Converts `self` to the target integer type, panicking if the value - /// does not lie in the target type's domain. + /// is not representable by the target type. /// /// # Panics /// - /// This function will panic if the value does not lie in the target type's domain. + /// This function will panic if the value is not representable by the target type. /// /// # Examples /// @@ -4131,7 +4131,8 @@ macro_rules! int_impl { T::strict_cast_from(self) } - /// Converts `self` to the target integer type, assuming the value lies in the target type's domain. + /// Converts `self` to the target integer type, assuming the value is + /// representable by the target type. /// /// # Safety /// diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 7419df618b7b5..b48e39752ad02 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -4257,7 +4257,7 @@ macro_rules! uint_impl { } /// Converts `self` to the target integer type, returning `None` if the value - /// does not lie in the target type's domain. + /// is not representable by the target type. /// /// # Examples /// @@ -4275,11 +4275,11 @@ macro_rules! uint_impl { } /// Converts `self` to the target integer type, panicking if the value - /// does not lie in the target type's domain. + /// is not representable by the target type. /// /// # Panics /// - /// This function will panic if the value does not lie in the target type's domain. + /// This function will panic if the value is not representable by the target type. /// /// # Examples /// @@ -4303,7 +4303,8 @@ macro_rules! uint_impl { T::strict_cast_from(self) } - /// Converts `self` to the target integer type, assuming the value lies in the target type's domain. + /// Converts `self` to the target integer type, assuming the value is + /// representable by the target type. /// /// # Safety /// From d79b87b209c53c399ac2fd0eab835d3a483be5f5 Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Sun, 7 Jun 2026 11:43:00 +0200 Subject: [PATCH 4/4] Add tests --- library/coretests/tests/lib.rs | 2 + library/coretests/tests/num/cast.rs | 101 ++++++++++++++++++++++++++++ library/coretests/tests/num/mod.rs | 1 + 3 files changed, 104 insertions(+) create mode 100644 library/coretests/tests/num/cast.rs 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;