Skip to main content

toasty_core/stmt/
num.rs

1//! Numeric type support for [`Value`], [`Expr`], and [`Type`].
2//!
3//! This module uses the `impl_num!` macro to generate, for each integer type
4//! (`i8`..`u64`):
5//!
6//! - `Type::is_{ty}()` -- type predicate
7//! - `Value::to_{ty}()` / `Value::to_{ty}_unwrap()` -- cross-width conversion
8//! - `From<{ty}> for Value` / `TryFrom<Value> for {ty}`
9//! - `PartialEq<{ty}>` for both `Value` and `Expr`
10//!
11//! # Examples
12//!
13//! ```
14//! use toasty_core::stmt::{Value, Type};
15//!
16//! let v = Value::from(42_i64);
17//! assert_eq!(v, 42_i64);
18//! assert_eq!(v.to_i64(), Some(42));
19//! assert!(Type::I64.is_i64());
20//! ```
21
22use super::{Expr, Type, Value};
23
24macro_rules! try_from {
25    ($v:expr, $ty:ty) => {
26        match $v {
27            Value::I8(v) => <$ty>::try_from(v).ok(),
28            Value::I16(v) => <$ty>::try_from(v).ok(),
29            Value::I32(v) => <$ty>::try_from(v).ok(),
30            Value::I64(v) => <$ty>::try_from(v).ok(),
31            Value::U8(v) => <$ty>::try_from(v).ok(),
32            Value::U16(v) => <$ty>::try_from(v).ok(),
33            Value::U32(v) => <$ty>::try_from(v).ok(),
34            Value::U64(v) => <$ty>::try_from(v).ok(),
35            _ => None,
36        }
37    };
38}
39
40macro_rules! impl_num {
41    (
42        $(
43            $variant:ident($ty:ty) {
44                $to:ident
45                $to_unwrap:ident
46                $is:ident
47            } )*
48    ) => {
49        impl Type {
50            $(
51                /// Returns `true` if this type matches the corresponding integer variant.
52                pub fn $is(&self) -> bool {
53                    matches!(self, Self::$variant)
54                }
55            )*
56        }
57
58        impl Value {
59            $(
60                /// Attempts to convert this value to the target integer type.
61                ///
62                /// Returns `None` if the value is not an integer variant or is out of
63                /// range for the target type. Conversion works across all integer
64                /// widths and signedness.
65                pub fn $to(&self) -> Option<$ty> {
66                    try_from!(*self, $ty)
67                }
68
69                /// Converts this value to the target integer type, panicking on failure.
70                ///
71                /// # Panics
72                ///
73                /// Panics if the value is not an integer variant or is out of range.
74                #[track_caller]
75                pub fn $to_unwrap(&self) -> $ty {
76                    try_from!(*self, $ty).expect("out of range integral type conversion attempted")
77                }
78            )*
79        }
80
81        $(
82            impl PartialEq<$ty> for Value {
83                fn eq(&self, other: &$ty) -> bool {
84                    try_from!(*self, $ty).map(|v| v == *other).unwrap_or(false)
85                }
86            }
87
88            impl PartialEq<Value> for $ty {
89                fn eq(&self, other: &Value) -> bool {
90                    other.eq(self)
91                }
92            }
93
94            impl PartialEq<$ty> for Expr {
95                fn eq(&self, other: &$ty) -> bool {
96                    match self {
97                        Expr::Value(value) => value.eq(other),
98                        _ => false,
99                    }
100                }
101            }
102
103            impl PartialEq<Expr> for $ty {
104                fn eq(&self, other: &Expr) -> bool {
105                    other.eq(self)
106                }
107            }
108
109            impl From<$ty> for Value {
110                fn from(value: $ty) -> Self {
111                    Self::$variant(value)
112                }
113            }
114
115            impl From<&$ty> for Value {
116                fn from(value: &$ty) -> Self {
117                    Self::$variant(*value)
118                }
119            }
120
121            impl TryFrom<Value> for $ty {
122                type Error = crate::Error;
123
124                fn try_from(value: Value) -> crate::Result<Self> {
125                    value.$to().ok_or_else(|| {
126                        crate::Error::type_conversion(value.clone(), stringify!($ty))
127                    })
128                }
129            }
130
131            #[cfg(feature = "assert-struct")]
132            impl assert_struct::Like<$ty> for Value {
133                fn like(&self, pattern: &$ty) -> bool {
134                    try_from!(*self, $ty).map(|v| v == *pattern).unwrap_or(false)
135                }
136            }
137
138            #[cfg(feature = "assert-struct")]
139            impl assert_struct::Like<$ty> for Expr {
140                fn like(&self, pattern: &$ty) -> bool {
141                    match self {
142                        Expr::Value(value) => value.like(pattern),
143                        _ => false,
144                    }
145                }
146            }
147        )*
148    };
149}
150
151macro_rules! impl_float {
152    (
153        $(
154            $variant:ident($ty:ty) {
155                $to:ident
156                $to_unwrap:ident
157                $is:ident
158                $unwrap_msg:literal
159            }
160        )*
161    ) => {
162        impl Type {
163            $(
164                /// Returns `true` if this type matches the corresponding float variant.
165                pub fn $is(&self) -> bool {
166                    matches!(self, Self::$variant)
167                }
168            )*
169        }
170
171        $(
172            impl From<$ty> for Value {
173                fn from(value: $ty) -> Self {
174                    Self::$variant(value)
175                }
176            }
177
178            impl From<&$ty> for Value {
179                fn from(value: &$ty) -> Self {
180                    Self::$variant(*value)
181                }
182            }
183
184            impl Value {
185                /// Converts this value to the target float type, panicking on failure.
186                ///
187                /// # Panics
188                ///
189                /// Panics if the value is not a float variant or if a narrowing conversion
190                /// overflows.
191                #[track_caller]
192                pub fn $to_unwrap(&self) -> $ty {
193                    self.$to().expect($unwrap_msg)
194                }
195            }
196
197            impl TryFrom<Value> for $ty {
198                type Error = crate::Error;
199
200                fn try_from(value: Value) -> crate::Result<Self> {
201                    value.$to().ok_or_else(|| {
202                        crate::Error::type_conversion(value.clone(), stringify!($ty))
203                    })
204                }
205            }
206        )*
207    };
208}
209
210impl_float! {
211    F32(f32) {
212        to_f32
213        to_f32_unwrap
214        is_f32
215        "value is not a finite f32"
216    }
217    F64(f64) {
218        to_f64
219        to_f64_unwrap
220        is_f64
221        "value is not a float type"
222    }
223}
224
225impl Value {
226    /// Attempts to convert this value to `f32`.
227    ///
228    /// Returns `None` if the value is not a float variant, or if a `F64` value
229    /// overflows `f32` range (would produce infinity from a finite value).
230    pub fn to_f32(&self) -> Option<f32> {
231        match self {
232            Value::F32(v) => Some(*v),
233            Value::F64(v) => {
234                let converted = *v as f32;
235                if converted.is_infinite() && !v.is_infinite() {
236                    None
237                } else {
238                    Some(converted)
239                }
240            }
241            _ => None,
242        }
243    }
244
245    /// Attempts to convert this value to `f64`.
246    ///
247    /// Returns `None` if the value is not a float variant.
248    /// `F32 → f64` is always safe (widening conversion).
249    pub fn to_f64(&self) -> Option<f64> {
250        match self {
251            Value::F32(v) => Some(*v as f64),
252            Value::F64(v) => Some(*v),
253            _ => None,
254        }
255    }
256}
257
258impl_num! {
259    I8(i8) {
260        to_i8
261        to_i8_unwrap
262        is_i8
263    }
264    I16(i16) {
265        to_i16
266        to_i16_unwrap
267        is_i16
268    }
269    I32(i32) {
270        to_i32
271        to_i32_unwrap
272        is_i32
273    }
274    I64(i64) {
275        to_i64
276        to_i64_unwrap
277        is_i64
278    }
279    U8(u8) {
280        to_u8
281        to_u8_unwrap
282        is_u8
283    }
284    U16(u16) {
285        to_u16
286        to_u16_unwrap
287        is_u16
288    }
289    U32(u32) {
290        to_u32
291        to_u32_unwrap
292        is_u32
293    }
294    U64(u64) {
295        to_u64
296        to_u64_unwrap
297        is_u64
298    }
299}
300
301impl From<usize> for Value {
302    fn from(value: usize) -> Self {
303        Value::U64(value as u64)
304    }
305}
306
307impl From<&usize> for Value {
308    fn from(value: &usize) -> Self {
309        Value::U64(*value as u64)
310    }
311}
312
313impl From<isize> for Value {
314    fn from(value: isize) -> Self {
315        Value::I64(value as i64)
316    }
317}
318
319impl From<&isize> for Value {
320    fn from(value: &isize) -> Self {
321        Value::I64(*value as i64)
322    }
323}
324
325#[cfg(feature = "assert-struct")]
326impl assert_struct::Like<usize> for Value {
327    fn like(&self, pattern: &usize) -> bool {
328        usize::try_from(self)
329            .map(|v| v == *pattern)
330            .unwrap_or(false)
331    }
332}
333
334#[cfg(feature = "assert-struct")]
335impl assert_struct::Like<usize> for Expr {
336    fn like(&self, pattern: &usize) -> bool {
337        match self {
338            Expr::Value(v) => v.like(pattern),
339            _ => false,
340        }
341    }
342}
343
344#[cfg(feature = "assert-struct")]
345impl assert_struct::Like<isize> for Value {
346    fn like(&self, pattern: &isize) -> bool {
347        isize::try_from(self)
348            .map(|v| v == *pattern)
349            .unwrap_or(false)
350    }
351}
352
353#[cfg(feature = "assert-struct")]
354impl assert_struct::Like<isize> for Expr {
355    fn like(&self, pattern: &isize) -> bool {
356        match self {
357            Expr::Value(v) => v.like(pattern),
358            _ => false,
359        }
360    }
361}
362
363// Pointer-sized integers convert from their fixed-size equivalents
364impl TryFrom<Value> for usize {
365    type Error = crate::Error;
366
367    fn try_from(value: Value) -> crate::Result<Self> {
368        (&value).try_into()
369    }
370}
371
372impl TryFrom<&Value> for usize {
373    type Error = crate::Error;
374
375    fn try_from(value: &Value) -> crate::Result<Self> {
376        let u64_val = value
377            .to_u64()
378            .ok_or_else(|| crate::Error::type_conversion(value.clone(), "usize"))?;
379        u64_val
380            .try_into()
381            .map_err(|_| crate::Error::type_conversion(Value::U64(u64_val), "usize"))
382    }
383}
384
385impl TryFrom<Value> for isize {
386    type Error = crate::Error;
387
388    fn try_from(value: Value) -> crate::Result<Self> {
389        (&value).try_into()
390    }
391}
392
393impl TryFrom<&Value> for isize {
394    type Error = crate::Error;
395
396    fn try_from(value: &Value) -> crate::Result<Self> {
397        let i64_val = value
398            .to_i64()
399            .ok_or_else(|| crate::Error::type_conversion(value.clone(), "isize"))?;
400        i64_val
401            .try_into()
402            .map_err(|_| crate::Error::type_conversion(Value::I64(i64_val), "isize"))
403    }
404}