From 6aefcc86d3cf48b05184efb9b2c1c719819d880f Mon Sep 17 00:00:00 2001 From: pickx Date: Fri, 14 Jul 2023 20:20:27 +0300 Subject: [PATCH] Implement `core::fmt::Step` for `UInt` (#30) * implement core::fmt::Step behind a feature * more tests * github actions workflow with this feature enabled * make range inclusive to test boundary * update changelog (1.2.7) * step_backward test is wrong! (it produced empty ranges) * add suggested tests, test steps_between --- .github/workflows/test-step.yml | 19 +++++++++ CHANGELOG.md | 6 +++ Cargo.toml | 3 ++ src/lib.rs | 34 ++++++++++++++++ tests/tests.rs | 70 +++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 .github/workflows/test-step.yml diff --git a/.github/workflows/test-step.yml b/.github/workflows/test-step.yml new file mode 100644 index 0000000..e68e8d9 --- /dev/null +++ b/.github/workflows/test-step.yml @@ -0,0 +1,19 @@ +name: test const +run-name: ${{ github.actor }}'s patch +on: [push] +jobs: + build-and-test: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '14' + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features step_trait \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 845ee45..4c79a54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## arbitrary-int 1.2.7 + +### Added + +- Support `Step` so that arbitrary-int can be used in a range expression, e.g. `for n in u3::MIN..=u3::MAX { println!("{n}") }`. Note this trait is currently unstable, and so is only usable in nightly. Enable this feature with `step_trait`. + ## arbitrary-int 1.2.6 ### Added diff --git a/Cargo.toml b/Cargo.toml index 12d7b57..4a1a295 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,5 +18,8 @@ std = [] # (2023-04-20 is broken, 2022-11-23 works. The exact day is somewhere inbetween) const_convert_and_const_trait_impl = [] +# core::fmt::Step is currently unstable and is available on nightly behind a feature gate +step_trait = [] + [dependencies] num-traits = { version = "0.2.15", default-features = false, optional = true } diff --git a/src/lib.rs b/src/lib.rs index 5b83a4b..7b926ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,12 @@ feature = "const_convert_and_const_trait_impl", feature(const_convert, const_trait_impl) )] +#![cfg_attr(feature = "step_trait", feature(step_trait))] use core::fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, UpperHex}; use core::hash::{Hash, Hasher}; +#[cfg(feature = "step_trait")] +use core::iter::Step; #[cfg(feature = "num-traits")] use core::num::Wrapping; use core::ops::{ @@ -718,6 +721,37 @@ where } } +#[cfg(feature = "step_trait")] +impl Step for UInt +where + Self: Number, + T: Copy + Step, +{ + #[inline] + fn steps_between(start: &Self, end: &Self) -> Option { + Step::steps_between(&start.value(), &end.value()) + } + + #[inline] + fn forward_checked(start: Self, count: usize) -> Option { + if let Some(res) = Step::forward_checked(start.value(), count) { + Self::try_new(res).ok() + } else { + None + } + } + + #[inline] + fn backward_checked(start: Self, count: usize) -> Option { + if let Some(res) = Step::backward_checked(start.value(), count) { + Self::try_new(res).ok() + } else { + None + } + } +} + + #[cfg(feature = "num-traits")] impl num_traits::WrappingAdd for UInt where diff --git a/tests/tests.rs b/tests/tests.rs index 45dd724..51c1fbe 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,6 +2,8 @@ extern crate core; use arbitrary_int::*; use std::collections::HashMap; +#[cfg(feature = "step_trait")] +use std::iter::Step; #[test] fn constants() { @@ -1301,3 +1303,71 @@ fn rotate_right() { assert_eq!(u24::new(0xEC0FFE), u24::new(0xC0FFEE).rotate_right(4)); } + +#[cfg(feature = "step_trait")] +#[test] +fn range_agrees_with_underlying() { + compare_range(u19::MIN, u19::MAX); + compare_range(u37::new(95_993), u37::new(1_994_910)); + compare_range(u68::new(58_858_348), u68::new(58_860_000)); + compare_range(u122::new(111_222_333_444), u122::new(111_222_444_555)); + compare_range(u5::MIN, u5::MAX); + compare_range(u23::MIN, u23::MAX); + compare_range(u48::new(999_444), u48::new(1_005_000)); + compare_range(u99::new(12345), u99::new(54321)); + + fn compare_range(arb_start: UInt, arb_end: UInt) + where + T: Copy + Step, + UInt: Step, + { + let arbint_range = (arb_start..=arb_end).map(UInt::value); + let underlying_range = arb_start.value()..=arb_end.value(); + + assert!(arbint_range.eq(underlying_range)); + } +} + +#[cfg(feature = "step_trait")] +#[test] +fn forward_checked() { + // In range + assert_eq!(Some(u7::new(121)), Step::forward_checked(u7::new(120), 1)); + assert_eq!(Some(u7::new(127)), Step::forward_checked(u7::new(120), 7)); + + // Out of range + assert_eq!(None, Step::forward_checked(u7::new(120), 8)); + + // Out of range for the underlying type + assert_eq!(None, Step::forward_checked(u7::new(120), 140)); +} + +#[cfg(feature = "step_trait")] +#[test] +fn backward_checked() { + // In range + assert_eq!(Some(u7::new(1)), Step::backward_checked(u7::new(10), 9)); + assert_eq!(Some(u7::new(0)), Step::backward_checked(u7::new(10), 10)); + + // Out of range (for both the arbitrary int and and the underlying type) + assert_eq!(None, Step::backward_checked(u7::new(10), 11)); +} + +#[cfg(feature = "step_trait")] +#[test] +fn steps_between() { + assert_eq!(Some(0), Step::steps_between(&u50::new(50), &u50::new(50))); + + assert_eq!(Some(4), Step::steps_between(&u24::new(5), &u24::new(9))); + assert_eq!(None, Step::steps_between(&u24::new(9), &u24::new(5))); + + // this assumes usize is <= 64 bits. a test like this one exists in `core::iter::step`. + assert_eq!( + Some(usize::MAX), + Step::steps_between(&u125::new(0x7), &u125::new(0x1_0000_0000_0000_0006)) + ); + assert_eq!( + None, + Step::steps_between(&u125::new(0x7), &u125::new(0x1_0000_0000_0000_0007)) + ); +}