From d837cf67003c150a5adf7fd3e94ae97ca80c5b94 Mon Sep 17 00:00:00 2001 From: Shun Sakai Date: Sat, 7 Feb 2026 13:42:49 +0900 Subject: [PATCH] feat: Implement `int_from_ascii` for `NonZero` --- library/core/src/num/nonzero.rs | 120 ++++++++++++++++++++++++++--- library/coretests/tests/lib.rs | 1 + library/coretests/tests/nonzero.rs | 56 ++++++++++++++ 3 files changed, 166 insertions(+), 11 deletions(-) diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index 16de01406d8c0..7876fced1c986 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1241,9 +1241,54 @@ macro_rules! nonzero_integer { unsafe { Self::new_unchecked(self.get().saturating_pow(other)) } } - /// Parses a non-zero integer from a string slice with digits in a given base. + /// Parses a non-zero integer from an ASCII-byte slice with decimal digits. /// - /// The string is expected to be an optional + /// The characters are expected to be an optional + #[doc = sign_dependent_expr!{ + $signedness ? + if signed { + " `+` or `-` " + } + if unsigned { + " `+` " + } + }] + /// sign followed by only digits. Leading and trailing non-digit characters (including + /// whitespace) represent an error. Underscores (which are accepted in Rust literals) + /// also represent an error. + /// + /// # Examples + /// + /// ``` + /// #![feature(int_from_ascii)] + /// + /// # use std::num::NonZero; + /// # + /// # fn main() { test().unwrap(); } + /// # fn test() -> Option<()> { + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_ascii(b\"+10\"), Ok(NonZero::new(10)?));")] + /// # Some(()) + /// # } + /// ``` + /// + /// Trailing space returns error: + /// + /// ``` + /// #![feature(int_from_ascii)] + /// + /// # use std::num::NonZero; + /// # + #[doc = concat!("assert!(NonZero::<", stringify!($Int), ">::from_ascii(b\"1 \").is_err());")] + /// ``` + #[unstable(feature = "int_from_ascii", issue = "134821")] + #[inline] + pub const fn from_ascii(src: &[u8]) -> Result { + Self::from_ascii_radix(src, 10) + } + + /// Parses a non-zero integer from an ASCII-byte slice with digits in a given base. + /// + /// The characters are expected to be an optional #[doc = sign_dependent_expr!{ $signedness ? if signed { @@ -1269,16 +1314,14 @@ macro_rules! nonzero_integer { /// /// # Examples /// - /// Basic usage: - /// /// ``` - /// #![feature(nonzero_from_str_radix)] + /// #![feature(int_from_ascii)] /// /// # use std::num::NonZero; /// # /// # fn main() { test().unwrap(); } /// # fn test() -> Option<()> { - #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_str_radix(\"A\", 16), Ok(NonZero::new(10)?));")] + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_ascii_radix(b\"A\", 16), Ok(NonZero::new(10)?));")] /// # Some(()) /// # } /// ``` @@ -1286,16 +1329,16 @@ macro_rules! nonzero_integer { /// Trailing space returns error: /// /// ``` - /// #![feature(nonzero_from_str_radix)] + /// #![feature(int_from_ascii)] /// /// # use std::num::NonZero; /// # - #[doc = concat!("assert!(NonZero::<", stringify!($Int), ">::from_str_radix(\"1 \", 10).is_err());")] + #[doc = concat!("assert!(NonZero::<", stringify!($Int), ">::from_ascii_radix(b\"1 \", 10).is_err());")] /// ``` - #[unstable(feature = "nonzero_from_str_radix", issue = "152193")] + #[unstable(feature = "int_from_ascii", issue = "134821")] #[inline] - pub const fn from_str_radix(src: &str, radix: u32) -> Result { - let n = match <$Int>::from_str_radix(src, radix) { + pub const fn from_ascii_radix(src: &[u8], radix: u32) -> Result { + let n = match <$Int>::from_ascii_radix(src, radix) { Ok(n) => n, Err(err) => return Err(err), }; @@ -1305,6 +1348,61 @@ macro_rules! nonzero_integer { Err(ParseIntError { kind: IntErrorKind::Zero }) } } + + /// Parses a non-zero integer from a string slice with digits in a given base. + /// + /// The string is expected to be an optional + #[doc = sign_dependent_expr!{ + $signedness ? + if signed { + " `+` or `-` " + } + if unsigned { + " `+` " + } + }] + /// sign followed by only digits. Leading and trailing non-digit characters (including + /// whitespace) represent an error. Underscores (which are accepted in Rust literals) + /// also represent an error. + /// + /// Digits are a subset of these characters, depending on `radix`: + /// + /// - `0-9` + /// - `a-z` + /// - `A-Z` + /// + /// # Panics + /// + /// This method panics if `radix` is not in the range from 2 to 36. + /// + /// # Examples + /// + /// ``` + /// #![feature(nonzero_from_str_radix)] + /// + /// # use std::num::NonZero; + /// # + /// # fn main() { test().unwrap(); } + /// # fn test() -> Option<()> { + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_str_radix(\"A\", 16), Ok(NonZero::new(10)?));")] + /// # Some(()) + /// # } + /// ``` + /// + /// Trailing space returns error: + /// + /// ``` + /// #![feature(nonzero_from_str_radix)] + /// + /// # use std::num::NonZero; + /// # + #[doc = concat!("assert!(NonZero::<", stringify!($Int), ">::from_str_radix(\"1 \", 10).is_err());")] + /// ``` + #[unstable(feature = "nonzero_from_str_radix", issue = "152193")] + #[inline] + pub const fn from_str_radix(src: &str, radix: u32) -> Result { + Self::from_ascii_radix(src.as_bytes(), radix) + } } #[stable(feature = "nonzero_parse", since = "1.35.0")] diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 91a7c898b2999..5923328655524 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -66,6 +66,7 @@ #![feature(generic_assert_internals)] #![feature(hasher_prefixfree_extras)] #![feature(hashmap_internals)] +#![feature(int_from_ascii)] #![feature(int_lowest_highest_one)] #![feature(int_roundings)] #![feature(ip)] diff --git a/library/coretests/tests/nonzero.rs b/library/coretests/tests/nonzero.rs index 134f875925f97..861e9e05081fc 100644 --- a/library/coretests/tests/nonzero.rs +++ b/library/coretests/tests/nonzero.rs @@ -124,6 +124,62 @@ fn test_from_signed_nonzero() { assert_eq!(num, 1i32); } +#[test] +fn test_from_ascii_radix() { + assert_eq!(NonZero::::from_ascii_radix(b"123", 10), Ok(NonZero::new(123).unwrap())); + assert_eq!(NonZero::::from_ascii_radix(b"1001", 2), Ok(NonZero::new(9).unwrap())); + assert_eq!(NonZero::::from_ascii_radix(b"123", 8), Ok(NonZero::new(83).unwrap())); + assert_eq!(NonZero::::from_ascii_radix(b"123", 16), Ok(NonZero::new(291).unwrap())); + assert_eq!(NonZero::::from_ascii_radix(b"ffff", 16), Ok(NonZero::new(65535).unwrap())); + assert_eq!(NonZero::::from_ascii_radix(b"z", 36), Ok(NonZero::new(35).unwrap())); + assert_eq!( + NonZero::::from_ascii_radix(b"0", 10).err().map(|e| e.kind().clone()), + Some(IntErrorKind::Zero) + ); + assert_eq!( + NonZero::::from_ascii_radix(b"-1", 10).err().map(|e| e.kind().clone()), + Some(IntErrorKind::InvalidDigit) + ); + assert_eq!( + NonZero::::from_ascii_radix(b"-129", 10).err().map(|e| e.kind().clone()), + Some(IntErrorKind::NegOverflow) + ); + assert_eq!( + NonZero::::from_ascii_radix(b"257", 10).err().map(|e| e.kind().clone()), + Some(IntErrorKind::PosOverflow) + ); + + assert_eq!( + NonZero::::from_ascii_radix(b"Z", 10).err().map(|e| e.kind().clone()), + Some(IntErrorKind::InvalidDigit) + ); + assert_eq!( + NonZero::::from_ascii_radix(b"_", 2).err().map(|e| e.kind().clone()), + Some(IntErrorKind::InvalidDigit) + ); +} + +#[test] +fn test_from_ascii() { + assert_eq!(NonZero::::from_ascii(b"123"), Ok(NonZero::new(123).unwrap())); + assert_eq!( + NonZero::::from_ascii(b"0").err().map(|e| e.kind().clone()), + Some(IntErrorKind::Zero) + ); + assert_eq!( + NonZero::::from_ascii(b"-1").err().map(|e| e.kind().clone()), + Some(IntErrorKind::InvalidDigit) + ); + assert_eq!( + NonZero::::from_ascii(b"-129").err().map(|e| e.kind().clone()), + Some(IntErrorKind::NegOverflow) + ); + assert_eq!( + NonZero::::from_ascii(b"257").err().map(|e| e.kind().clone()), + Some(IntErrorKind::PosOverflow) + ); +} + #[test] fn test_from_str_radix() { assert_eq!(NonZero::::from_str_radix("123", 10), Ok(NonZero::new(123).unwrap()));