From dfe9f8da5a594f80d5417f7cb3e8d6bbe52f3ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jalil=20David=20Salam=C3=A9=20Messina?= Date: Tue, 9 Jul 2024 19:21:54 +0200 Subject: [PATCH 1/2] feat(core): impl Step for NonZero Implement Step for NonZero unsigned integers as discussed in [libs-team#130][1]. [1]: https://github.com/rust-lang/libs-team/issues/130 `step_nonzero_impls` was adapted from `step_integer_impls` and the tests were adapted from the step tests. --- library/core/src/iter/range.rs | 150 +++++++++++++++++++++++++++++++ library/core/tests/iter/range.rs | 67 ++++++++++++++ 2 files changed, 217 insertions(+) diff --git a/library/core/src/iter/range.rs b/library/core/src/iter/range.rs index 644a169294396..6c3a39cedccf2 100644 --- a/library/core/src/iter/range.rs +++ b/library/core/src/iter/range.rs @@ -16,6 +16,7 @@ macro_rules! unsafe_impl_trusted_step { )*}; } unsafe_impl_trusted_step![AsciiChar char i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize Ipv4Addr Ipv6Addr]; +unsafe_impl_trusted_step![NonZero NonZero NonZero NonZero NonZero NonZero]; /// Objects that have a notion of *successor* and *predecessor* operations. /// @@ -431,6 +432,138 @@ step_integer_impls! { wider than usize: [u32 i32], [u64 i64], [u128 i128]; } +// These are still macro-generated because the integer literals resolve to different types. +macro_rules! step_nonzero_identical_methods { + ($int:ident) => { + #[inline] + unsafe fn forward_unchecked(start: Self, n: usize) -> Self { + // SAFETY: the caller has to guarantee that `start + n` doesn't overflow. + unsafe { Self::new_unchecked(start.get().unchecked_add(n as $int)) } + } + + #[inline] + unsafe fn backward_unchecked(start: Self, n: usize) -> Self { + // SAFETY: the caller has to guarantee that `start - n` doesn't overflow or hit zero. + unsafe { Self::new_unchecked(start.get().unchecked_sub(n as $int)) } + } + + #[inline] + #[allow(arithmetic_overflow)] + #[rustc_inherit_overflow_checks] + fn forward(start: Self, n: usize) -> Self { + // In debug builds, trigger a panic on overflow. + // This should optimize completely out in release builds. + if Self::forward_checked(start, n).is_none() { + let _ = $int::MAX + 1; + } + // Do saturating math (wrapping math causes UB if it wraps to Zero) + start.saturating_add(n as $int) + } + + #[inline] + #[allow(arithmetic_overflow)] + #[rustc_inherit_overflow_checks] + fn backward(start: Self, n: usize) -> Self { + // In debug builds, trigger a panic on overflow. + // This should optimize completely out in release builds. + if Self::backward_checked(start, n).is_none() { + let _ = $int::MIN - 1; + } + // Do saturating math (wrapping math causes UB if it wraps to Zero) + Self::new(start.get().saturating_sub(n as $int)).unwrap_or(Self::MIN) + } + }; +} + +macro_rules! step_nonzero_impls { + { + narrower than or same width as usize: + $( $narrower:ident ),+; + wider than usize: + $( $wider:ident ),+; + } => { + $( + #[allow(unreachable_patterns)] + #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")] + impl Step for NonZero<$narrower> { + step_nonzero_identical_methods!($narrower); + + #[inline] + fn steps_between(start: &Self, end: &Self) -> Option { + if *start <= *end { + // This relies on $u_narrower <= usize + Some((end.get() - start.get()) as usize) + } else { + None + } + } + + #[inline] + fn forward_checked(start: Self, n: usize) -> Option { + match $narrower::try_from(n) { + Ok(n) => start.checked_add(n), + Err(_) => None, // if n is out of range, `unsigned_start + n` is too + } + } + + #[inline] + fn backward_checked(start: Self, n: usize) -> Option { + match $narrower::try_from(n) { + // *_sub() is not implemented on NonZero + Ok(n) => start.get().checked_sub(n).and_then(Self::new), + Err(_) => None, // if n is out of range, `unsigned_start - n` is too + } + } + } + )+ + + $( + #[allow(unreachable_patterns)] + #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")] + impl Step for NonZero<$wider> { + step_nonzero_identical_methods!($wider); + + #[inline] + fn steps_between(start: &Self, end: &Self) -> Option { + if *start <= *end { + usize::try_from(end.get() - start.get()).ok() + } else { + None + } + } + + #[inline] + fn forward_checked(start: Self, n: usize) -> Option { + start.checked_add(n as $wider) + } + + #[inline] + fn backward_checked(start: Self, n: usize) -> Option { + start.get().checked_sub(n as $wider).and_then(Self::new) + } + } + )+ + }; +} + +#[cfg(target_pointer_width = "64")] +step_nonzero_impls! { + narrower than or same width as usize: u8, u16, u32, u64, usize; + wider than usize: u128; +} + +#[cfg(target_pointer_width = "32")] +step_nonzero_impls! { + narrower than or same width as usize: u8, u16, u32, usize; + wider than usize: u64, u128; +} + +#[cfg(target_pointer_width = "16")] +step_nonzero_impls! { + narrower than or same width as usize: u8, u16, usize; + wider than usize: u32, u64, u128; +} + #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")] impl Step for char { #[inline] @@ -923,6 +1056,7 @@ impl Iterator for ops::Range { range_exact_iter_impl! { usize u8 u16 isize i8 i16 + NonZero NonZero NonZero // These are incorrect per the reasoning above, // but removing them would be a breaking change as they were stabilized in Rust 1.0.0. @@ -932,25 +1066,35 @@ range_exact_iter_impl! { i32 } +#[cfg(target_pointer_width = "32")] +range_exact_iter_impl! { NonZero } + +#[cfg(target_pointer_width = "64")] +range_exact_iter_impl! { NonZero } + unsafe_range_trusted_random_access_impl! { usize u8 u16 isize i8 i16 + NonZero NonZero NonZero } #[cfg(target_pointer_width = "32")] unsafe_range_trusted_random_access_impl! { u32 i32 + NonZero } #[cfg(target_pointer_width = "64")] unsafe_range_trusted_random_access_impl! { u32 i32 u64 i64 + NonZero NonZero } range_incl_exact_iter_impl! { u8 i8 + NonZero // These are incorrect per the reasoning above, // but removing them would be a breaking change as they were stabilized in Rust 1.26.0. @@ -960,6 +1104,12 @@ range_incl_exact_iter_impl! { i16 } +#[cfg(target_pointer_width = "32")] +range_incl_exact_iter_impl! { NonZero } + +#[cfg(target_pointer_width = "64")] +range_incl_exact_iter_impl! { NonZero } + #[stable(feature = "rust1", since = "1.0.0")] impl DoubleEndedIterator for ops::Range { #[inline] diff --git a/library/core/tests/iter/range.rs b/library/core/tests/iter/range.rs index e31db0732e094..be5422499b5ef 100644 --- a/library/core/tests/iter/range.rs +++ b/library/core/tests/iter/range.rs @@ -501,3 +501,70 @@ fn test_double_ended_range() { panic!("unreachable"); } } + +macro_rules! nz { + ($type:ident($val:literal)) => { + $type::new($val).unwrap() + }; +} + +macro_rules! nonzero_array { + ($type:ident[$($val:literal),*]) => { + [$(nz!($type($val))),*] + } +} + +macro_rules! nonzero_range { + ($type:ident($($left:literal)?..$($right:literal)?)) => { + nz!($type($($left)?))..nz!($type($($right)?)) + }; + ($type:ident($($left:literal)?..=$right:literal)) => { + nz!($type($($left)?))..=nz!($type($right)) + }; +} + +#[test] +fn test_nonzero_range() { + #![allow(deprecated)] + use core::num::{NonZeroU32, NonZeroU8, NonZeroUsize}; + + assert_eq!( + nonzero_range!(NonZeroUsize(1..=21)).step_by(5).collect::>(), + nonzero_array!(NonZeroUsize[1, 6, 11, 16, 21]) + ); + assert_eq!( + nonzero_range!(NonZeroUsize(1..=20)).step_by(5).collect::>(), + nonzero_array!(NonZeroUsize[1, 6, 11, 16]) + ); + assert_eq!( + nonzero_range!(NonZeroUsize(1..20)).step_by(5).collect::>(), + nonzero_array!(NonZeroUsize[1, 6, 11, 16]) + ); + assert_eq!( + nonzero_range!(NonZeroUsize(1..21)).rev().step_by(5).collect::>(), + nonzero_array!(NonZeroUsize[20, 15, 10, 5]) + ); + assert_eq!( + nonzero_range!(NonZeroUsize(1..21)).rev().step_by(6).collect::>(), + nonzero_array!(NonZeroUsize[20, 14, 8, 2]) + ); + assert_eq!( + nonzero_range!(NonZeroU8(200..255)).step_by(50).collect::>(), + nonzero_array!(NonZeroU8[200, 250]) + ); + assert_eq!( + nonzero_range!(NonZeroUsize(200..5)).step_by(1).collect::>(), + nonzero_array!(NonZeroUsize[]) + ); + assert_eq!( + nonzero_range!(NonZeroUsize(200..200)).step_by(1).collect::>(), + nonzero_array!(NonZeroUsize[]) + ); + + assert_eq!(nonzero_range!(NonZeroU32(10..20)).step_by(1).size_hint(), (10, Some(10))); + assert_eq!(nonzero_range!(NonZeroU32(10..20)).step_by(5).size_hint(), (2, Some(2))); + assert_eq!(nonzero_range!(NonZeroU32(1..21)).rev().step_by(5).size_hint(), (4, Some(4))); + assert_eq!(nonzero_range!(NonZeroU32(1..21)).rev().step_by(6).size_hint(), (4, Some(4))); + assert_eq!(nonzero_range!(NonZeroU32(20..1)).step_by(1).size_hint(), (0, Some(0))); + assert_eq!(nonzero_range!(NonZeroU32(20..20)).step_by(1).size_hint(), (0, Some(0))); +} From 18adf5f587a27fb60f02a79226b746c8d69e32b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jalil=20David=20Salam=C3=A9=20Messina?= Date: Tue, 9 Jul 2024 19:21:54 +0200 Subject: [PATCH 2/2] chore: update ui tests Implementing `Step` for the `NonZero` changed the diagnostics from the compiler. --- .../impl-trait/impl_trait_projections.stderr | 24 +++++++++---------- tests/ui/range/range-1.stderr | 12 +++++----- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/ui/impl-trait/impl_trait_projections.stderr b/tests/ui/impl-trait/impl_trait_projections.stderr index d62e3ac4183f5..bc77f551a2602 100644 --- a/tests/ui/impl-trait/impl_trait_projections.stderr +++ b/tests/ui/impl-trait/impl_trait_projections.stderr @@ -38,12 +38,12 @@ LL | -> <::std::ops::Range as Iterator>::Item Char Ipv4Addr Ipv6Addr - char - i128 - i16 - i32 - i64 - and 8 others + NonZero + NonZero + NonZero + NonZero + NonZero + and 14 others = note: required for `std::ops::Range` to implement `Iterator` error[E0277]: the trait bound `impl Debug: Step` is not satisfied @@ -59,12 +59,12 @@ LL | | } Char Ipv4Addr Ipv6Addr - char - i128 - i16 - i32 - i64 - and 8 others + NonZero + NonZero + NonZero + NonZero + NonZero + and 14 others = note: required for `std::ops::Range` to implement `Iterator` error: aborting due to 7 previous errors diff --git a/tests/ui/range/range-1.stderr b/tests/ui/range/range-1.stderr index f98420557c625..e50c68e3ab173 100644 --- a/tests/ui/range/range-1.stderr +++ b/tests/ui/range/range-1.stderr @@ -14,12 +14,12 @@ LL | for i in false..true {} Char Ipv4Addr Ipv6Addr - char - i128 - i16 - i32 - i64 - and 8 others + NonZero + NonZero + NonZero + NonZero + NonZero + and 14 others = note: required for `std::ops::Range` to implement `Iterator` = note: required for `std::ops::Range` to implement `IntoIterator`