Repository: typst/typst
File: crates/typst-library/src/foundations/int.rs (Rust)

Code before:
    /// ```
    #[func(constructor)]
    pub fn construct(
        /// The value that should be converted to an integer.
        value: Spanned<ToInt>,
        /// The base (radix) for parsing strings, between 2 and 36.
        #[named]
        #[default(Spanned::new(Base::Default, Span::detached()))]
        base: Spanned<Base>,
    ) -> SourceResult<i64> {
        Ok(match value.v {
            ToInt::Int(n) => {
                if matches!(base.v, Base::User(_)) {
                    bail!(base.span, "base is only supported for strings");
                }

                n
            }
            ToInt::Str(s) => {
                let b = base.v.value();
                if !(2..=36).contains(&b) {
                    bail!(base.span, "base must be between 2 and 36");
                }

                // Replace minus sign at the start with `-`
                let s: &str = if s.as_str().starts_with(repr::MINUS_SIGN) {
                    &s.as_str().replacen(repr::MINUS_SIGN, "-", 1)
                } else {
                    &s
                };
                match std::primitive::i64::from_str_radix(s, b.try_into().unwrap()) {
                    Ok(n) => n,
                    Err(e) => {
                        let error = match e.kind() {
                            IntErrorKind::Empty => "can't parse an empty string",
                            IntErrorKind::InvalidDigit => {
                                "invalid digit found while parsing integer"
                            }
                            IntErrorKind::PosOverflow => {
                                "the integer is too large to fit into a signed 64-bit integer"
                            }
                            IntErrorKind::NegOverflow => {
                                "the integer is too small to fit into a signed 64-bit integer"
                            }
                            // This shouldn't ever happen :)
                            IntErrorKind::Zero => "zero couldn't be represented",
                            _ => "unknown error",
                        };
                        bail!(value.span, "invalid integer: {error}")
                    }
                }

Review comment (performance):
this is a bit inefficient. could you we do the sign handling separately to avoid the allocation? (I presume we might run into issues with max/min integers like we did before?)

Code after:
    /// ```
    #[func(constructor)]
    pub fn construct(
        /// The value that should be converted to an integer.
        value: Spanned<ToInt>,
        /// The base (radix) for parsing strings, between 2 and 36.
        #[named]
        #[default(Spanned::new(Base::Default, Span::detached()))]
        base: Spanned<Base>,
    ) -> SourceResult<i64> {
        Ok(match value.v {
            ToInt::Int(n) => match base.v {
                Base::User(_) => bail!(base.span, "base is only supported for strings"),
                _ => n,
            },
            ToInt::Str(s) => {
                let base_value = base.v.value();
                let radix: u32 = match base_value {
                    2..=36 => base_value as u32,
                    _ => bail!(base.span, "base must be between 2 and 36"),
                };

                match s.strip_prefix('-').or_else(|| s.strip_prefix(repr::MINUS_SIGN)) {
                    // Negative (without minus)
                    Some(s) => {
                        // Parse the digits part into u64
                        //  => abs(i64::MIN) fits into u64
                        let bigger = u64::from_str_radix(s, radix)
                            .map_err(|e| parse_str_error(e.kind(), base))
                            .at(value.span)?;

                        // Number wouldn't fit into i64
                        if bigger > i64::MIN.unsigned_abs() {
                            return Err(parse_str_error(
                                &IntErrorKind::NegOverflow,
                                base,
                            ))
                            .at(value.span);
                        }

                        bigger.wrapping_neg() as i64
                    }
                    // Positive
                    None => i64::from_str_radix(&s, radix)
                        .map_err(|e| parse_str_error(e.kind(), base))
                        .at(value.span)?,
                }
            }
        })
    }