From 07537b403fad8720003aab23bc763796f2a28c02 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 31 Jan 2026 04:24:47 -0600 Subject: [PATCH] num: Move user-public float parsing items directly under core::num Follow up to the previous commit refactoring `core::num` by moving the user-facing error types and trait implementations back out of `imp` and into a new module under `core::num`. --- library/core/src/num/float_parse.rs | 133 ++++++++++++++++++++++ library/core/src/num/imp/dec2flt/mod.rs | 141 ++---------------------- library/core/src/num/mod.rs | 4 +- tests/codegen-llvm/overflow-checks.rs | 2 +- 4 files changed, 145 insertions(+), 135 deletions(-) create mode 100644 library/core/src/num/float_parse.rs diff --git a/library/core/src/num/float_parse.rs b/library/core/src/num/float_parse.rs new file mode 100644 index 000000000000..d8846a0cc0a8 --- /dev/null +++ b/library/core/src/num/float_parse.rs @@ -0,0 +1,133 @@ +//! User-facing API for float parsing. + +use crate::error::Error; +use crate::fmt; +use crate::num::imp::dec2flt; +use crate::str::FromStr; + +macro_rules! from_str_float_impl { + ($t:ty) => { + #[stable(feature = "rust1", since = "1.0.0")] + impl FromStr for $t { + type Err = ParseFloatError; + + /// Converts a string in base 10 to a float. + /// Accepts an optional decimal exponent. + /// + /// This function accepts strings such as + /// + /// * '3.14' + /// * '-3.14' + /// * '2.5E10', or equivalently, '2.5e10' + /// * '2.5E-10' + /// * '5.' + /// * '.5', or, equivalently, '0.5' + /// * '7' + /// * '007' + /// * 'inf', '-inf', '+infinity', 'NaN' + /// + /// Note that alphabetical characters are not case-sensitive. + /// + /// Leading and trailing whitespace represent an error. + /// + /// # Grammar + /// + /// All strings that adhere to the following [EBNF] grammar when + /// lowercased will result in an [`Ok`] being returned: + /// + /// ```txt + /// Float ::= Sign? ( 'inf' | 'infinity' | 'nan' | Number ) + /// Number ::= ( Digit+ | + /// Digit+ '.' Digit* | + /// Digit* '.' Digit+ ) Exp? + /// Exp ::= 'e' Sign? Digit+ + /// Sign ::= [+-] + /// Digit ::= [0-9] + /// ``` + /// + /// [EBNF]: https://www.w3.org/TR/REC-xml/#sec-notation + /// + /// # Arguments + /// + /// * src - A string + /// + /// # Return value + /// + /// `Err(ParseFloatError)` if the string did not represent a valid + /// number. Otherwise, `Ok(n)` where `n` is the closest + /// representable floating-point number to the number represented + /// by `src` (following the same rules for rounding as for the + /// results of primitive operations). + // We add the `#[inline(never)]` attribute, since its content will + // be filled with that of `dec2flt`, which has #[inline(always)]. + // Since `dec2flt` is generic, a normal inline attribute on this function + // with `dec2flt` having no attributes results in heavily repeated + // generation of `dec2flt`, despite the fact only a maximum of 2 + // possible instances can ever exist. Adding #[inline(never)] avoids this. + #[inline(never)] + fn from_str(src: &str) -> Result { + dec2flt::dec2flt(src) + } + } + }; +} + +#[cfg(target_has_reliable_f16)] +from_str_float_impl!(f16); +from_str_float_impl!(f32); +from_str_float_impl!(f64); + +// FIXME(f16): A fallback is used when the backend+target does not support f16 well, in order +// to avoid ICEs. + +#[cfg(not(target_has_reliable_f16))] +#[expect(ineffective_unstable_trait_impl, reason = "stable trait on unstable type")] +#[unstable(feature = "f16", issue = "116909")] +impl FromStr for f16 { + type Err = ParseFloatError; + + #[inline] + fn from_str(_src: &str) -> Result { + unimplemented!("requires target_has_reliable_f16") + } +} + +/// An error which can be returned when parsing a float. +/// +/// This error is used as the error type for the [`FromStr`] implementation +/// for [`f32`] and [`f64`]. +/// +/// # Example +/// +/// ``` +/// use std::str::FromStr; +/// +/// if let Err(e) = f64::from_str("a.12") { +/// println!("Failed conversion to f64: {e}"); +/// } +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +#[stable(feature = "rust1", since = "1.0.0")] +pub struct ParseFloatError { + pub(super) kind: FloatErrorKind, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) enum FloatErrorKind { + Empty, + Invalid, +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl Error for ParseFloatError {} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Display for ParseFloatError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + FloatErrorKind::Empty => "cannot parse float from empty string", + FloatErrorKind::Invalid => "invalid float literal", + } + .fmt(f) + } +} diff --git a/library/core/src/num/imp/dec2flt/mod.rs b/library/core/src/num/imp/dec2flt/mod.rs index 66e30e1c5f7f..3f5724add62b 100644 --- a/library/core/src/num/imp/dec2flt/mod.rs +++ b/library/core/src/num/imp/dec2flt/mod.rs @@ -87,14 +87,14 @@ issue = "none" )] -use self::common::BiasedFp; -use self::float::RawFloat; -use self::lemire::compute_float; -use self::parse::{parse_inf_nan, parse_number}; -use self::slow::parse_long_mantissa; -use crate::error::Error; -use crate::fmt; -use crate::str::FromStr; +use common::BiasedFp; +use float::RawFloat; +use lemire::compute_float; +use parse::{parse_inf_nan, parse_number}; +use slow::parse_long_mantissa; + +use crate::num::ParseFloatError; +use crate::num::float_parse::FloatErrorKind; mod common; pub mod decimal; @@ -107,131 +107,6 @@ pub mod lemire; pub mod parse; -macro_rules! from_str_float_impl { - ($t:ty) => { - #[stable(feature = "rust1", since = "1.0.0")] - impl FromStr for $t { - type Err = ParseFloatError; - - /// Converts a string in base 10 to a float. - /// Accepts an optional decimal exponent. - /// - /// This function accepts strings such as - /// - /// * '3.14' - /// * '-3.14' - /// * '2.5E10', or equivalently, '2.5e10' - /// * '2.5E-10' - /// * '5.' - /// * '.5', or, equivalently, '0.5' - /// * '7' - /// * '007' - /// * 'inf', '-inf', '+infinity', 'NaN' - /// - /// Note that alphabetical characters are not case-sensitive. - /// - /// Leading and trailing whitespace represent an error. - /// - /// # Grammar - /// - /// All strings that adhere to the following [EBNF] grammar when - /// lowercased will result in an [`Ok`] being returned: - /// - /// ```txt - /// Float ::= Sign? ( 'inf' | 'infinity' | 'nan' | Number ) - /// Number ::= ( Digit+ | - /// Digit+ '.' Digit* | - /// Digit* '.' Digit+ ) Exp? - /// Exp ::= 'e' Sign? Digit+ - /// Sign ::= [+-] - /// Digit ::= [0-9] - /// ``` - /// - /// [EBNF]: https://www.w3.org/TR/REC-xml/#sec-notation - /// - /// # Arguments - /// - /// * src - A string - /// - /// # Return value - /// - /// `Err(ParseFloatError)` if the string did not represent a valid - /// number. Otherwise, `Ok(n)` where `n` is the closest - /// representable floating-point number to the number represented - /// by `src` (following the same rules for rounding as for the - /// results of primitive operations). - // We add the `#[inline(never)]` attribute, since its content will - // be filled with that of `dec2flt`, which has #[inline(always)]. - // Since `dec2flt` is generic, a normal inline attribute on this function - // with `dec2flt` having no attributes results in heavily repeated - // generation of `dec2flt`, despite the fact only a maximum of 2 - // possible instances can ever exist. Adding #[inline(never)] avoids this. - #[inline(never)] - fn from_str(src: &str) -> Result { - dec2flt(src) - } - } - }; -} - -#[cfg(target_has_reliable_f16)] -from_str_float_impl!(f16); -from_str_float_impl!(f32); -from_str_float_impl!(f64); - -// FIXME(f16): A fallback is used when the backend+target does not support f16 well, in order -// to avoid ICEs. - -#[cfg(not(target_has_reliable_f16))] -impl FromStr for f16 { - type Err = ParseFloatError; - - #[inline] - fn from_str(_src: &str) -> Result { - unimplemented!("requires target_has_reliable_f16") - } -} - -/// An error which can be returned when parsing a float. -/// -/// This error is used as the error type for the [`FromStr`] implementation -/// for [`f32`] and [`f64`]. -/// -/// # Example -/// -/// ``` -/// use std::str::FromStr; -/// -/// if let Err(e) = f64::from_str("a.12") { -/// println!("Failed conversion to f64: {e}"); -/// } -/// ``` -#[derive(Debug, Clone, PartialEq, Eq)] -#[stable(feature = "rust1", since = "1.0.0")] -pub struct ParseFloatError { - kind: FloatErrorKind, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -enum FloatErrorKind { - Empty, - Invalid, -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl Error for ParseFloatError {} - -#[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Display for ParseFloatError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.kind { - FloatErrorKind::Empty => "cannot parse float from empty string", - FloatErrorKind::Invalid => "invalid float literal", - } - .fmt(f) - } -} - #[inline] pub(super) fn pfe_empty() -> ParseFloatError { ParseFloatError { kind: FloatErrorKind::Empty } diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index ff8c1df32ecb..0f2397d47d87 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -42,6 +42,8 @@ macro_rules! sign_dependent_expr { mod uint_macros; // import uint_impl! mod error; +#[cfg(not(no_fp_fmt_parse))] +mod float_parse; mod nonzero; mod saturating; mod wrapping; @@ -58,7 +60,7 @@ macro_rules! sign_dependent_expr { pub use error::TryFromIntError; #[stable(feature = "rust1", since = "1.0.0")] #[cfg(not(no_fp_fmt_parse))] -pub use imp::dec2flt::ParseFloatError; +pub use float_parse::ParseFloatError; #[stable(feature = "generic_nonzero", since = "1.79.0")] pub use nonzero::NonZero; #[unstable( diff --git a/tests/codegen-llvm/overflow-checks.rs b/tests/codegen-llvm/overflow-checks.rs index 3a48e6b76243..a9d5b2d2ec07 100644 --- a/tests/codegen-llvm/overflow-checks.rs +++ b/tests/codegen-llvm/overflow-checks.rs @@ -21,7 +21,7 @@ pub unsafe fn add(a: u8, b: u8) -> u8 { // CHECK: i8 noundef{{( zeroext)?}} %a, i8 noundef{{( zeroext)?}} %b // CHECK: add i8 %b, %a // DEBUG: icmp ult i8 [[zero:[^,]+]], %a - // DEBUG: call core::num::overflow_panic::add + // DEBUG: call core::num::imp::overflow_panic::add // DEBUG: unreachable // NOCHECKS-NOT: unreachable // NOCHECKS: ret i8 %0