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 16de01406d8c..7876fced1c98 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1241,6 +1241,114 @@ macro_rules! nonzero_integer { unsafe { Self::new_unchecked(self.get().saturating_pow(other)) } } + /// Parses a non-zero integer from an ASCII-byte slice with decimal digits. + /// + /// 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 { + " `+` 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(int_from_ascii)] + /// + /// # use std::num::NonZero; + /// # + /// # fn main() { test().unwrap(); } + /// # fn test() -> Option<()> { + #[doc = concat!("assert_eq!(NonZero::<", stringify!($Int), ">::from_ascii_radix(b\"A\", 16), 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_radix(b\"1 \", 10).is_err());")] + /// ``` + #[unstable(feature = "int_from_ascii", issue = "134821")] + #[inline] + 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), + }; + if let Some(n) = Self::new(n) { + Ok(n) + } else { + 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 @@ -1269,8 +1377,6 @@ macro_rules! nonzero_integer { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// #![feature(nonzero_from_str_radix)] /// @@ -1295,15 +1401,7 @@ macro_rules! nonzero_integer { #[unstable(feature = "nonzero_from_str_radix", issue = "152193")] #[inline] pub const fn from_str_radix(src: &str, radix: u32) -> Result { - let n = match <$Int>::from_str_radix(src, radix) { - Ok(n) => n, - Err(err) => return Err(err), - }; - if let Some(n) = Self::new(n) { - Ok(n) - } else { - Err(ParseIntError { kind: IntErrorKind::Zero }) - } + Self::from_ascii_radix(src.as_bytes(), radix) } } diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 91a7c898b299..592332865552 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 134f875925f9..861e9e05081f 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()));