use std::{convert::TryFrom, fmt, ops::Deref}; use rb_sys::{rb_ll2inum, rb_to_int, rb_ull2inum, ruby_special_consts, ruby_value_type, VALUE}; use crate::{ debug_assert_value, error::{protect, Error}, exception, r_bignum::RBignum, try_convert::TryConvert, value::{private, Fixnum, NonZeroValue, ReprValue, Value}, }; pub(crate) enum IntegerType { Fixnum(Fixnum), Bignum(RBignum), } /// A type wrapping either a [`Fixnum`] or a [`RBignum`]. /// /// 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 Integer(NonZeroValue); impl Integer { /// Return `Some(Integer)` if `val` is an `Integer`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Integer::from_value(eval("0").unwrap()).is_some()); /// assert!(Integer::from_value(eval("9223372036854775807").unwrap()).is_some()); /// // not an int /// assert!(Integer::from_value(eval("1.23").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { if val.as_rb_value() & ruby_special_consts::RUBY_FIXNUM_FLAG as VALUE != 0 { return Some(Self(NonZeroValue::new_unchecked(val))); } debug_assert_value!(val); (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))) } pub(crate) fn integer_type(self) -> IntegerType { unsafe { if self.as_rb_value() & ruby_special_consts::RUBY_FIXNUM_FLAG as VALUE != 0 { IntegerType::Fixnum(Fixnum::from_rb_value_unchecked(self.as_rb_value())) } else { IntegerType::Bignum(RBignum::from_rb_value_unchecked(self.as_rb_value())) } } } /// Create a new `Integer` from an `i64.` /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let res: bool = eval!("i == 0", i = Integer::from_i64(0)).unwrap(); /// assert!(res); /// let res: bool = eval!("i == 4611686018427387904", i = Integer::from_i64(4611686018427387904)).unwrap(); /// assert!(res); /// let res: bool = eval!("i == -4611686018427387905", i = Integer::from_i64(-4611686018427387905)).unwrap(); /// assert!(res); /// ``` #[inline] pub fn from_i64(n: i64) -> Self { unsafe { Self::from_rb_value_unchecked( Fixnum::from_i64_impl(n) .map(|f| f.as_rb_value()) .unwrap_or_else(|| rb_ll2inum(n)), ) } } /// Create a new `Integer` from a `u64.` /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let res: bool = eval!("i == 0", i = Integer::from_u64(0)).unwrap(); /// assert!(res); /// let res: bool = eval!("i == 4611686018427387904", i = Integer::from_u64(4611686018427387904)).unwrap(); /// assert!(res); /// ``` #[inline] pub fn from_u64(n: u64) -> Self { unsafe { Self::from_rb_value_unchecked( Fixnum::from_i64_impl(i64::try_from(n).unwrap_or(i64::MAX)) .map(|f| f.as_rb_value()) .unwrap_or_else(|| rb_ull2inum(n)), ) } } /// Convert `self` to an `i8`. Returns `Err` if `self` is out of range for /// `i8`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("127").unwrap().to_i8().unwrap(), 127); /// assert!(eval::("128").unwrap().to_i8().is_err()); /// assert_eq!(eval::("-128").unwrap().to_i8().unwrap(), -128); /// assert!(eval::("-129").unwrap().to_i8().is_err()); /// ``` #[inline] pub fn to_i8(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_i8(), IntegerType::Bignum(_) => Err(Error::new( exception::range_error(), "bignum too big to convert into `i8`", )), } } /// Convert `self` to an `i16`. Returns `Err` if `self` is out of range for /// `i16`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("32767").unwrap().to_i16().unwrap(), 32767); /// assert!(eval::("32768").unwrap().to_i16().is_err()); /// assert_eq!(eval::("-32768").unwrap().to_i16().unwrap(), -32768); /// assert!(eval::("-32769").unwrap().to_i16().is_err()); /// ``` #[inline] pub fn to_i16(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_i16(), IntegerType::Bignum(_) => Err(Error::new( exception::range_error(), "bignum too big to convert into `i16`", )), } } /// Convert `self` to an `i32`. Returns `Err` if `self` is out of range for /// `i32`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("2147483647").unwrap().to_i32().unwrap(), 2147483647); /// assert!(eval::("2147483648").unwrap().to_i32().is_err()); /// assert_eq!(eval::("-2147483648").unwrap().to_i32().unwrap(), -2147483648); /// assert!(eval::("-2147483649").unwrap().to_i32().is_err()); /// ``` #[inline] pub fn to_i32(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_i32(), IntegerType::Bignum(big) => big.to_i32(), } } /// Convert `self` to an `i64`. Returns `Err` if `self` is out of range for /// `i64`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("4611686018427387903").unwrap().to_i64().unwrap(), 4611686018427387903); /// assert_eq!(eval::("-4611686018427387904").unwrap().to_i64().unwrap(), -4611686018427387904); /// assert!(eval::("9223372036854775808").unwrap().to_i64().is_err()); /// assert!(eval::("-9223372036854775809").unwrap().to_i64().is_err()); /// ``` #[inline] pub fn to_i64(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => Ok(fix.to_i64()), IntegerType::Bignum(big) => big.to_i64(), } } /// Convert `self` to an `isize`. Returns `Err` if `self` is out of range /// for `isize`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("4611686018427387903").unwrap().to_isize().unwrap(), 4611686018427387903); /// assert_eq!(eval::("-4611686018427387904").unwrap().to_isize().unwrap(), -4611686018427387904); /// ``` #[inline] pub fn to_isize(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => Ok(fix.to_isize()), IntegerType::Bignum(big) => big.to_isize(), } } /// Convert `self` to a `u8`. Returns `Err` if `self` is negative or out of /// range for `u8`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("255").unwrap().to_u8().unwrap(), 255); /// assert!(eval::("256").unwrap().to_u8().is_err()); /// assert!(eval::("-1").unwrap().to_u8().is_err()); /// ``` #[inline] pub fn to_u8(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_u8(), IntegerType::Bignum(_) => Err(Error::new( exception::range_error(), "bignum too big to convert into `u8`", )), } } /// Convert `self` to a `u16`. Returns `Err` if `self` is negative or out /// of range for `u16`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("65535").unwrap().to_u16().unwrap(), 65535); /// assert!(eval::("65536").unwrap().to_u16().is_err()); /// assert!(eval::("-1").unwrap().to_u16().is_err()); /// ``` #[inline] pub fn to_u16(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_u16(), IntegerType::Bignum(_) => Err(Error::new( exception::range_error(), "bignum too big to convert into `u16`", )), } } /// Convert `self` to a `u32`. Returns `Err` if `self` is negative or out /// of range for `u32`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("4294967295").unwrap().to_u32().unwrap(), 4294967295); /// assert!(eval::("4294967296").unwrap().to_u32().is_err()); /// assert!(eval::("-1").unwrap().to_u32().is_err()); /// ``` #[inline] pub fn to_u32(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_u32(), IntegerType::Bignum(big) => big.to_u32(), } } /// Convert `self` to a `u64`. Returns `Err` if `self` is negative or out /// of range for `u64`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("4611686018427387903").unwrap().to_u64().unwrap(), 4611686018427387903); /// assert!(eval::("-1").unwrap().to_u64().is_err()); /// assert!(eval::("18446744073709551616").unwrap().to_u64().is_err()); /// ``` #[inline] pub fn to_u64(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_u64(), IntegerType::Bignum(big) => big.to_u64(), } } /// Convert `self` to a `usize`. Returns `Err` if `self` is negative or out /// of range for `usize`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("4611686018427387903").unwrap().to_usize().unwrap(), 4611686018427387903); /// assert!(eval::("-1").unwrap().to_usize().is_err()); /// ``` #[inline] pub fn to_usize(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_usize(), IntegerType::Bignum(big) => big.to_usize(), } } } impl Deref for Integer { type Target = Value; fn deref(&self) -> &Self::Target { self.0.get_ref() } } impl fmt::Display for Integer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Integer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl From for Value { fn from(val: Integer) -> Self { *val } } unsafe impl private::ReprValue for Integer { fn to_value(self) -> Value { *self } unsafe fn from_value_unchecked(val: Value) -> Self { Self(NonZeroValue::new_unchecked(val)) } } impl ReprValue for Integer {} impl TryConvert for Integer { fn try_convert(val: Value) -> Result { match Self::from_value(val) { Some(i) => Ok(i), None => protect(|| { debug_assert_value!(val); unsafe { Self::from_rb_value_unchecked(rb_to_int(val.as_rb_value())) } }), } } }