artichoke_backend/convert/
boolean.rs

1use crate::Artichoke;
2use crate::convert::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<bool, Value> for Artichoke {
10    fn convert(&self, value: bool) -> Value {
11        // SAFETY: Boolean Ruby Values do not need to be protected because they
12        // are immediates and do not live on the mruby heap.
13        if value {
14            Value::from(unsafe { sys::mrb_sys_true_value() })
15        } else {
16            Value::from(unsafe { sys::mrb_sys_false_value() })
17        }
18    }
19}
20
21impl Convert<Option<bool>, Value> for Artichoke {
22    fn convert(&self, value: Option<bool>) -> Value {
23        let Some(value) = value else {
24            return Value::nil();
25        };
26        self.convert(value)
27    }
28}
29
30impl TryConvert<Value, bool> for Artichoke {
31    type Error = Error;
32
33    fn try_convert(&self, value: Value) -> Result<bool, Self::Error> {
34        let Ruby::Bool = value.ruby_type() else {
35            return Err(UnboxRubyError::new(&value, Rust::Bool).into());
36        };
37        let inner = value.inner();
38        if sys::mrb_sys_value_is_true(inner) {
39            Ok(true)
40        } else if sys::mrb_sys_value_is_false(inner) {
41            Ok(false)
42        } else {
43            // This branch is unreachable because `Ruby::Bool` typed values
44            // are guaranteed to be either true or false.
45            Err(UnboxRubyError::new(&value, Rust::Bool).into())
46        }
47    }
48}
49
50impl TryConvert<Value, Option<bool>> for Artichoke {
51    type Error = Error;
52
53    fn try_convert(&self, value: Value) -> Result<Option<bool>, Self::Error> {
54        if value.is_nil() {
55            Ok(None)
56        } else {
57            Ok(Some(self.try_convert(value)?))
58        }
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use crate::test::prelude::*;
65
66    #[test]
67    fn fail_convert() {
68        let mut interp = interpreter();
69        // get a Ruby value that can't be converted to a primitive type.
70        let value = interp.eval(b"Object.new").unwrap();
71        let result = value.try_convert_into::<bool>(&interp);
72        assert!(result.is_err());
73    }
74
75    #[test]
76    fn prop_convert_to_bool() {
77        let interp = interpreter();
78        for b in [true, false] {
79            let value = interp.convert(b);
80            assert_eq!(value.ruby_type(), Ruby::Bool);
81        }
82    }
83
84    #[test]
85    fn prop_convert_to_nilable_bool() {
86        let interp = interpreter();
87        for b in [Some(true), Some(false), None] {
88            let value = interp.convert(b);
89            if b.is_some() {
90                assert_eq!(value.ruby_type(), Ruby::Bool);
91            } else {
92                assert_eq!(value.ruby_type(), Ruby::Nil);
93            }
94        }
95    }
96
97    #[test]
98    fn test_bool_true_with_value() {
99        let interp = interpreter();
100        let value = interp.convert(true);
101        let inner = value.inner();
102        // When true, the inner value should be true and not false or nil.
103        assert!(!sys::mrb_sys_value_is_false(inner));
104        assert!(sys::mrb_sys_value_is_true(inner));
105        assert!(!sys::mrb_sys_value_is_nil(inner));
106    }
107
108    #[test]
109    fn test_bool_false_with_value() {
110        let interp = interpreter();
111        let value = interp.convert(false);
112        let inner = value.inner();
113        // When false, the inner value should be false and not true or nil.
114        assert!(sys::mrb_sys_value_is_false(inner));
115        assert!(!sys::mrb_sys_value_is_true(inner));
116        assert!(!sys::mrb_sys_value_is_nil(inner));
117    }
118
119    #[test]
120    fn test_convert_some_true() {
121        let interp = interpreter();
122        let value = interp.convert(Some(true));
123        let inner = value.inner();
124        // For Some(true), the inner value should be true.
125        assert!(!sys::mrb_sys_value_is_false(inner));
126        assert!(sys::mrb_sys_value_is_true(inner));
127        assert!(!sys::mrb_sys_value_is_nil(inner));
128    }
129
130    #[test]
131    fn test_convert_some_false() {
132        let interp = interpreter();
133        let value = interp.convert(Some(false));
134        let inner = value.inner();
135        // For Some(false), the inner value should be false.
136        assert!(sys::mrb_sys_value_is_false(inner));
137        assert!(!sys::mrb_sys_value_is_true(inner));
138        assert!(!sys::mrb_sys_value_is_nil(inner));
139    }
140
141    #[test]
142    fn test_convert_none() {
143        let interp = interpreter();
144        let value = interp.convert(None::<bool>);
145        let inner = value.inner();
146        // For None, the converted value should be Ruby's nil.
147        assert!(!sys::mrb_sys_value_is_false(inner));
148        assert!(!sys::mrb_sys_value_is_true(inner));
149        assert!(sys::mrb_sys_value_is_nil(inner));
150    }
151
152    #[test]
153    fn prop_roundtrip() {
154        let interp = interpreter();
155        for b in [true, false] {
156            let value = interp.convert(b);
157            let roundtrip: bool = value.try_convert_into(&interp).unwrap();
158            assert_eq!(roundtrip, b);
159        }
160    }
161
162    #[test]
163    fn prop_nilable_roundtrip() {
164        let interp = interpreter();
165        for b in [Some(true), Some(false), None] {
166            let value = interp.convert(b);
167            let roundtrip: Option<bool> = value.try_convert_into(&interp).unwrap();
168            assert_eq!(roundtrip, b);
169        }
170    }
171
172    #[test]
173    fn prop_roundtrip_err() {
174        let interp = interpreter();
175        run_arbitrary::<i64>(|i_val| {
176            let value = interp.convert(i_val);
177            let result: Result<bool, _> = value.try_convert_into(&interp);
178            assert!(result.is_err());
179        });
180    }
181}