artichoke_backend/convert/
fixnum.rs

1use crate::Artichoke;
2use crate::convert::{BoxIntoRubyError, UnboxRubyError};
3use crate::core::{Convert, TryConvert, Value as _};
4use crate::error::Error;
5use crate::sys;
6use crate::types::{Ruby, Rust};
7use crate::value::Value;
8
9impl Convert<u8, Value> for Artichoke {
10    #[inline]
11    fn convert(&self, value: u8) -> Value {
12        self.convert(i64::from(value))
13    }
14}
15
16impl Convert<u16, Value> for Artichoke {
17    #[inline]
18    fn convert(&self, value: u16) -> Value {
19        self.convert(i64::from(value))
20    }
21}
22
23impl Convert<u32, Value> for Artichoke {
24    #[inline]
25    fn convert(&self, value: u32) -> Value {
26        self.convert(i64::from(value))
27    }
28}
29
30impl TryConvert<u64, Value> for Artichoke {
31    type Error = Error;
32
33    fn try_convert(&self, value: u64) -> Result<Value, Self::Error> {
34        let Ok(value) = i64::try_from(value) else {
35            return Err(BoxIntoRubyError::new(Rust::UnsignedInt, Ruby::Fixnum).into());
36        };
37        // SAFETY: `i64` Ruby Values do not need to be protected because they
38        // are immediates and do not live on the mruby heap.
39        // `mrb_sys_fixnum_value` is a safe ffi call when given an `i64`.
40        let fixnum = unsafe { sys::mrb_sys_fixnum_value(value) };
41        Ok(Value::from(fixnum))
42    }
43}
44
45impl TryConvert<usize, Value> for Artichoke {
46    type Error = Error;
47
48    fn try_convert(&self, value: usize) -> Result<Value, Self::Error> {
49        let Ok(value) = i64::try_from(value) else {
50            return Err(BoxIntoRubyError::new(Rust::UnsignedInt, Ruby::Fixnum).into());
51        };
52        // SAFETY: `i64` Ruby Values do not need to be protected because they
53        // are immediates and do not live on the mruby heap.
54        // `mrb_sys_fixnum_value` is a safe ffi call when given an `i64`.
55        let fixnum = unsafe { sys::mrb_sys_fixnum_value(value) };
56        Ok(Value::from(fixnum))
57    }
58}
59
60impl Convert<i8, Value> for Artichoke {
61    #[inline]
62    fn convert(&self, value: i8) -> Value {
63        self.convert(i64::from(value))
64    }
65}
66
67impl Convert<i16, Value> for Artichoke {
68    #[inline]
69    fn convert(&self, value: i16) -> Value {
70        self.convert(i64::from(value))
71    }
72}
73
74impl Convert<i32, Value> for Artichoke {
75    #[inline]
76    fn convert(&self, value: i32) -> Value {
77        self.convert(i64::from(value))
78    }
79}
80
81impl TryConvert<isize, Value> for Artichoke {
82    type Error = Error;
83
84    fn try_convert(&self, value: isize) -> Result<Value, Self::Error> {
85        let Ok(value) = i64::try_from(value) else {
86            return Err(BoxIntoRubyError::new(Rust::SignedInt, Ruby::Fixnum).into());
87        };
88        // SAFETY: `i64` Ruby Values do not need to be protected because they
89        // are immediates and do not live on the mruby heap.
90        // `mrb_sys_fixnum_value` is a safe ffi call when given an `i64`.
91        let fixnum = unsafe { sys::mrb_sys_fixnum_value(value) };
92        Ok(Value::from(fixnum))
93    }
94}
95
96impl Convert<i64, Value> for Artichoke {
97    #[inline]
98    fn convert(&self, value: i64) -> Value {
99        // SAFETY: `i64` Ruby Values do not need to be protected because they
100        // are immediates and do not live on the mruby heap.
101        // `mrb_sys_fixnum_value` is a safe ffi call when given an `i64`.
102        let fixnum = unsafe { sys::mrb_sys_fixnum_value(value) };
103        Value::from(fixnum)
104    }
105}
106
107impl TryConvert<Value, i64> for Artichoke {
108    type Error = Error;
109
110    fn try_convert(&self, value: Value) -> Result<i64, Self::Error> {
111        let Ruby::Fixnum = value.ruby_type() else {
112            return Err(UnboxRubyError::new(&value, Rust::SignedInt).into());
113        };
114        let inner = value.inner();
115        // SAFETY: value is validated to have integer type tag.
116        Ok(unsafe { sys::mrb_sys_fixnum_to_cint(inner) })
117    }
118}
119
120impl TryConvert<Value, u32> for Artichoke {
121    type Error = Error;
122
123    fn try_convert(&self, value: Value) -> Result<u32, Self::Error> {
124        let Ruby::Fixnum = value.ruby_type() else {
125            return Err(UnboxRubyError::new(&value, Rust::SignedInt).into());
126        };
127        let inner = value.inner();
128        let num = unsafe { sys::mrb_sys_fixnum_to_cint(inner) };
129        let num = u32::try_from(num).map_err(|_| UnboxRubyError::new(&value, Rust::UnsignedInt))?;
130        Ok(num)
131    }
132}
133
134impl TryConvert<Value, usize> for Artichoke {
135    type Error = Error;
136
137    fn try_convert(&self, value: Value) -> Result<usize, Self::Error> {
138        let Ruby::Fixnum = value.ruby_type() else {
139            return Err(UnboxRubyError::new(&value, Rust::SignedInt).into());
140        };
141        let inner = value.inner();
142        let num = unsafe { sys::mrb_sys_fixnum_to_cint(inner) };
143        let num = usize::try_from(num).map_err(|_| UnboxRubyError::new(&value, Rust::UnsignedInt))?;
144        Ok(num)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use crate::test::prelude::*;
151
152    #[test]
153    fn fail_convert() {
154        let mut interp = interpreter();
155        // get a Ruby value that can't be converted to a primitive type.
156        let value = interp.eval(b"Object.new").unwrap();
157        let result = value.try_convert_into::<i64>(&interp);
158        assert!(result.is_err());
159    }
160
161    #[test]
162    fn prop_convert_to_fixnum() {
163        let interp = interpreter();
164        run_arbitrary::<i64>(|i| {
165            let value = interp.convert(i);
166            assert_eq!(value.ruby_type(), Ruby::Fixnum);
167        });
168    }
169
170    #[test]
171    fn prop_fixnum_with_value() {
172        let interp = interpreter();
173        run_arbitrary::<i64>(|i| {
174            let value = interp.convert(i);
175            let inner = value.inner();
176            let cint = unsafe { sys::mrb_sys_fixnum_to_cint(inner) };
177            assert_eq!(cint, i);
178        });
179    }
180
181    #[test]
182    fn prop_roundtrip() {
183        let interp = interpreter();
184        run_arbitrary::<i64>(|i| {
185            let value = interp.convert(i);
186            let value = value.try_convert_into::<i64>(&interp).unwrap();
187            assert_eq!(value, i);
188        });
189    }
190
191    #[test]
192    fn prop_roundtrip_err() {
193        let interp = interpreter();
194        for b in [true, false] {
195            let value = interp.convert(b);
196            let value = value.try_convert_into::<i64>(&interp);
197            assert!(value.is_err());
198        }
199    }
200
201    #[test]
202    fn test_fixnum_to_usize() {
203        let interp = interpreter();
204        let value = Convert::<_, Value>::convert(&*interp, 100);
205        let value = value.try_convert_into::<usize>(&interp).unwrap();
206        assert_eq!(100, value);
207        let value = Convert::<_, Value>::convert(&*interp, -100);
208        let value = value.try_convert_into::<usize>(&interp);
209        assert!(value.is_err());
210    }
211}