magnus 0.4.4

High level Ruby bindings for Rust.
Documentation
use std::{
    fmt,
    ops::Deref,
    os::raw::{c_long, c_longlong, c_ulong, c_ulonglong},
};

use rb_sys::{
    rb_ll2inum, rb_num2ll, rb_num2long, rb_num2ull, rb_num2ulong, rb_ull2inum, ruby_fl_type,
    ruby_value_type, VALUE,
};

use crate::{
    debug_assert_value,
    error::{protect, Error},
    exception,
    integer::{Integer, IntegerType},
    try_convert::TryConvert,
    value::{private, Fixnum, NonZeroValue, ReprValue, Value, QNIL},
};

/// A Value pointer to a RBignum struct, Ruby's internal representation of
/// large integers.
///
/// See also [`Integer`].
///
/// All [`Value`] methods should be available on this type through [`Deref`],
/// but some may be missed by this documentation.
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct RBignum(NonZeroValue);

impl RBignum {
    /// Return `Some(RBignum)` if `val` is a `RBignum`, `None` otherwise.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, RBignum};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// assert!(RBignum::from_value(eval("9223372036854775807").unwrap()).is_some());
    /// // too small
    /// assert!(RBignum::from_value(eval("0").unwrap()).is_none());
    /// // not an int
    /// assert!(RBignum::from_value(eval("1.23").unwrap()).is_none());
    /// ```
    #[inline]
    pub fn from_value(val: Value) -> Option<Self> {
        unsafe {
            (val.rb_type() == ruby_value_type::RUBY_T_BIGNUM)
                .then(|| Self(NonZeroValue::new_unchecked(val)))
        }
    }

    #[inline]
    pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self {
        Self(NonZeroValue::new_unchecked(Value::new(val)))
    }

    /// Create a new `RBignum` from an `i64.`
    ///
    /// Returns `Ok(RBignum)` if `n` is large enough to require a bignum,
    /// otherwise returns `Err(Fixnum)`.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, RBignum};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// assert!(RBignum::from_i64(4611686018427387904).is_ok());
    /// assert!(RBignum::from_i64(-4611686018427387905).is_ok());
    /// // too small
    /// assert!(RBignum::from_i64(0).is_err());
    /// ```
    pub fn from_i64(n: i64) -> Result<Self, Fixnum> {
        unsafe {
            let val = Value::new(rb_ll2inum(n));
            RBignum::from_value(val)
                .ok_or_else(|| Fixnum::from_rb_value_unchecked(val.as_rb_value()))
        }
    }

    /// Create a new `RBignum` from an `u64.`
    ///
    /// Returns `Ok(RBignum)` if `n` is large enough to require a bignum,
    /// otherwise returns `Err(Fixnum)`.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, RBignum};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// assert!(RBignum::from_u64(4611686018427387904).is_ok());
    /// // too small
    /// assert!(RBignum::from_u64(0).is_err());
    /// ```
    pub fn from_u64(n: u64) -> Result<Self, Fixnum> {
        unsafe {
            let val = Value::new(rb_ull2inum(n));
            RBignum::from_value(val)
                .ok_or_else(|| Fixnum::from_rb_value_unchecked(val.as_rb_value()))
        }
    }

    fn is_negative(self) -> bool {
        debug_assert_value!(self);
        unsafe {
            let r_basic = self.r_basic_unchecked();
            r_basic.as_ref().flags & (ruby_fl_type::RUBY_FL_USER1 as VALUE) == 0
        }
    }

    /// Create a new `RBignum` from a `i32.`
    ///
    /// This will only succeed on a 32 bit system. On a 64 bit system bignum
    /// will always be out of range.
    #[doc(hidden)]
    pub fn to_i32(self) -> Result<i32, Error> {
        debug_assert_value!(self);
        let mut res = 0;
        protect(|| {
            res = unsafe { rb_num2long(self.as_rb_value()) };
            QNIL
        })?;
        if res > i32::MAX as c_long {
            return Err(Error::new(
                exception::range_error(),
                "bignum too big to convert into `i32`",
            ));
        }
        Ok(res as i32)
    }

    /// Convert `self` to an `i64`. Returns `Err` if `self` is out of range for
    /// `i64`.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, RBignum};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// assert_eq!(eval::<RBignum>("4611686018427387904").unwrap().to_i64().unwrap(), 4611686018427387904);
    /// assert_eq!(eval::<RBignum>("-4611686018427387905").unwrap().to_i64().unwrap(), -4611686018427387905);
    /// assert!(eval::<RBignum>("9223372036854775808").unwrap().to_i64().is_err());
    /// assert!(eval::<RBignum>("-9223372036854775809").unwrap().to_i64().is_err());
    /// ```
    pub fn to_i64(self) -> Result<i64, Error> {
        debug_assert_value!(self);
        let mut res = 0;
        protect(|| {
            res = unsafe { rb_num2ll(self.as_rb_value()) };
            QNIL
        })?;
        Ok(res)
    }

    /// Convert `self` to an `isize`. Returns `Err` if `self` is out of range
    /// for `isize`.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, RBignum};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// assert_eq!(eval::<RBignum>("4611686018427387904").unwrap().to_isize().unwrap(), 4611686018427387904);
    /// assert_eq!(eval::<RBignum>("-4611686018427387905").unwrap().to_isize().unwrap(), -4611686018427387905);
    /// ```
    pub fn to_isize(self) -> Result<isize, Error> {
        debug_assert_value!(self);
        let mut res = 0;
        protect(|| {
            res = unsafe { rb_num2ll(self.as_rb_value()) };
            QNIL
        })?;
        if res > isize::MAX as c_longlong {
            return Err(Error::new(
                exception::range_error(),
                "bignum too big to convert into `isize`",
            ));
        }
        Ok(res as isize)
    }

    /// Create a new `RBignum` from a `u32.`
    ///
    /// This will only succeed on a 32 bit system. On a 64 bit system bignum
    /// will always be out of range.
    #[doc(hidden)]
    pub fn to_u32(self) -> Result<u32, Error> {
        debug_assert_value!(self);
        if self.is_negative() {
            return Err(Error::new(
                exception::range_error(),
                "can't convert negative integer to unsigned",
            ));
        }
        let mut res = 0;
        protect(|| {
            res = unsafe { rb_num2ulong(self.as_rb_value()) };
            QNIL
        })?;
        if res > u32::MAX as c_ulong {
            return Err(Error::new(
                exception::range_error(),
                "bignum too big to convert into `u32`",
            ));
        }
        Ok(res as u32)
    }

    /// Convert `self` to a `u64`. Returns `Err` if `self` is negative or out
    /// of range for `u64`.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, RBignum};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// assert_eq!(eval::<RBignum>("4611686018427387904").unwrap().to_u64().unwrap(), 4611686018427387904);
    /// assert!(eval::<RBignum>("18446744073709551616").unwrap().to_u64().is_err());
    /// ```
    pub fn to_u64(self) -> Result<u64, Error> {
        debug_assert_value!(self);
        if self.is_negative() {
            return Err(Error::new(
                exception::range_error(),
                "can't convert negative integer to unsigned",
            ));
        }
        let mut res = 0;
        protect(|| {
            res = unsafe { rb_num2ull(self.as_rb_value()) };
            QNIL
        })?;
        Ok(res)
    }

    /// Convert `self` to a `usize`. Returns `Err` if `self` is negative or out
    /// of range for `usize`.
    ///
    /// # Examples
    ///
    /// ```
    /// use magnus::{eval, RBignum};
    /// # let _cleanup = unsafe { magnus::embed::init() };
    ///
    /// assert_eq!(eval::<RBignum>("4611686018427387904").unwrap().to_usize().unwrap(), 4611686018427387904);
    /// assert!(eval::<RBignum>("18446744073709551616").unwrap().to_usize().is_err());
    /// ```
    pub fn to_usize(self) -> Result<usize, Error> {
        debug_assert_value!(self);
        if self.is_negative() {
            return Err(Error::new(
                exception::range_error(),
                "can't convert negative integer to unsigned",
            ));
        }
        let mut res = 0;
        protect(|| {
            res = unsafe { rb_num2ull(self.as_rb_value()) };
            QNIL
        })?;
        if res > usize::MAX as c_ulonglong {
            return Err(Error::new(
                exception::range_error(),
                "bignum too big to convert into `usize`",
            ));
        }
        Ok(res as usize)
    }
}

impl Deref for RBignum {
    type Target = Value;

    fn deref(&self) -> &Self::Target {
        self.0.get_ref()
    }
}

impl fmt::Display for RBignum {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", unsafe { self.to_s_infallible() })
    }
}

impl fmt::Debug for RBignum {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.inspect())
    }
}

impl From<RBignum> for Value {
    fn from(val: RBignum) -> Self {
        *val
    }
}

unsafe impl private::ReprValue for RBignum {
    fn to_value(self) -> Value {
        *self
    }

    unsafe fn from_value_unchecked(val: Value) -> Self {
        Self(NonZeroValue::new_unchecked(val))
    }
}

impl ReprValue for RBignum {}

impl TryConvert for RBignum {
    fn try_convert(val: Value) -> Result<Self, Error> {
        match val.try_convert::<Integer>()?.integer_type() {
            IntegerType::Fixnum(_) => Err(Error::new(
                exception::range_error(),
                "integer to small for bignum",
            )),
            IntegerType::Bignum(big) => Ok(big),
        }
    }
}