rune/runtime/value/
inline.rs

1use core::any;
2use core::cmp::Ordering;
3use core::fmt;
4use core::hash::Hash as _;
5
6use musli::{Decode, Encode};
7use serde::{Deserialize, Serialize};
8
9use crate as rune;
10use crate::runtime::{
11    Hasher, OwnedTuple, Protocol, RuntimeError, Type, TypeInfo, VmErrorKind, VmIntegerRepr,
12};
13use crate::{Hash, TypeHash};
14
15/// An inline value.
16#[derive(Clone, Copy, Encode, Decode, Deserialize, Serialize)]
17pub enum Inline {
18    /// An empty value.
19    ///
20    /// Note that this value *can not* be instantiated. Internally any
21    /// operations over it will result in a type error, even when operating with
22    /// itself.
23    ///
24    /// Some operations will return a "falsy" value, like type checks.
25    Empty,
26    /// The unit value.
27    Unit,
28    /// A boolean.
29    Bool(bool),
30    /// A character.
31    Char(char),
32    /// A number.
33    Signed(i64),
34    /// An unsigned number.
35    Unsigned(u64),
36    /// A float.
37    Float(f64),
38    /// A type hash. Describes a type in the virtual machine.
39    Type(Type),
40    /// Ordering.
41    Ordering(
42        #[musli(with = crate::musli::ordering)]
43        #[serde(with = "crate::serde::ordering")]
44        Ordering,
45    ),
46    /// A type hash.
47    Hash(Hash),
48}
49
50impl Inline {
51    pub(crate) fn as_integer<T>(self) -> Result<T, RuntimeError>
52    where
53        T: TryFrom<u64> + TryFrom<i64>,
54    {
55        match self {
56            Inline::Unsigned(value) => match value.try_into() {
57                Ok(number) => Ok(number),
58                Err(..) => Err(RuntimeError::new(
59                    VmErrorKind::ValueToIntegerCoercionError {
60                        from: VmIntegerRepr::from(value),
61                        to: any::type_name::<T>(),
62                    },
63                )),
64            },
65            Inline::Signed(value) => match value.try_into() {
66                Ok(number) => Ok(number),
67                Err(..) => Err(RuntimeError::new(
68                    VmErrorKind::ValueToIntegerCoercionError {
69                        from: VmIntegerRepr::from(value),
70                        to: any::type_name::<T>(),
71                    },
72                )),
73            },
74            ref value => Err(RuntimeError::new(VmErrorKind::ExpectedNumber {
75                actual: value.type_info(),
76            })),
77        }
78    }
79
80    /// Perform a partial equality check over two inline values.
81    pub(crate) fn partial_eq(&self, other: &Self) -> Result<bool, RuntimeError> {
82        match (self, other) {
83            (Inline::Unit, Inline::Unit) => Ok(true),
84            (Inline::Bool(a), Inline::Bool(b)) => Ok(*a == *b),
85            (Inline::Char(a), Inline::Char(b)) => Ok(*a == *b),
86            (Inline::Signed(a), Inline::Signed(b)) => Ok(*a == *b),
87            (Inline::Signed(a), rhs) => Ok(*a == rhs.as_integer::<i64>()?),
88            (Inline::Unsigned(a), Inline::Unsigned(b)) => Ok(*a == *b),
89            (Inline::Unsigned(a), rhs) => Ok(*a == rhs.as_integer::<u64>()?),
90            (Inline::Float(a), Inline::Float(b)) => Ok(*a == *b),
91            (Inline::Type(a), Inline::Type(b)) => Ok(*a == *b),
92            (Inline::Ordering(a), Inline::Ordering(b)) => Ok(*a == *b),
93            (Inline::Hash(a), Inline::Hash(b)) => Ok(*a == *b),
94            (lhs, rhs) => Err(RuntimeError::from(
95                VmErrorKind::UnsupportedBinaryOperation {
96                    op: Protocol::PARTIAL_EQ.name,
97                    lhs: lhs.type_info(),
98                    rhs: rhs.type_info(),
99                },
100            )),
101        }
102    }
103
104    /// Perform a total equality check over two inline values.
105    pub(crate) fn eq(&self, other: &Self) -> Result<bool, RuntimeError> {
106        match (self, other) {
107            (Inline::Unit, Inline::Unit) => Ok(true),
108            (Inline::Bool(a), Inline::Bool(b)) => Ok(*a == *b),
109            (Inline::Char(a), Inline::Char(b)) => Ok(*a == *b),
110            (Inline::Unsigned(a), Inline::Unsigned(b)) => Ok(*a == *b),
111            (Inline::Signed(a), Inline::Signed(b)) => Ok(*a == *b),
112            (Inline::Float(a), Inline::Float(b)) => {
113                let Some(ordering) = a.partial_cmp(b) else {
114                    return Err(RuntimeError::new(VmErrorKind::IllegalFloatComparison {
115                        lhs: *a,
116                        rhs: *b,
117                    }));
118                };
119
120                Ok(matches!(ordering, Ordering::Equal))
121            }
122            (Inline::Type(a), Inline::Type(b)) => Ok(*a == *b),
123            (Inline::Ordering(a), Inline::Ordering(b)) => Ok(*a == *b),
124            (Inline::Hash(a), Inline::Hash(b)) => Ok(*a == *b),
125            (lhs, rhs) => Err(RuntimeError::new(VmErrorKind::UnsupportedBinaryOperation {
126                op: Protocol::EQ.name,
127                lhs: lhs.type_info(),
128                rhs: rhs.type_info(),
129            })),
130        }
131    }
132
133    /// Partial comparison implementation for inline.
134    pub(crate) fn partial_cmp(&self, other: &Self) -> Result<Option<Ordering>, RuntimeError> {
135        match (self, other) {
136            (Inline::Unit, Inline::Unit) => Ok(Some(Ordering::Equal)),
137            (Inline::Bool(lhs), Inline::Bool(rhs)) => Ok(lhs.partial_cmp(rhs)),
138            (Inline::Char(lhs), Inline::Char(rhs)) => Ok(lhs.partial_cmp(rhs)),
139            (Inline::Unsigned(lhs), Inline::Unsigned(rhs)) => Ok(lhs.partial_cmp(rhs)),
140            (Inline::Unsigned(lhs), rhs) => {
141                let rhs = rhs.as_integer::<u64>()?;
142                Ok(lhs.partial_cmp(&rhs))
143            }
144            (Inline::Signed(lhs), Inline::Signed(rhs)) => Ok(lhs.partial_cmp(rhs)),
145            (Inline::Signed(lhs), rhs) => {
146                let rhs = rhs.as_integer::<i64>()?;
147                Ok(lhs.partial_cmp(&rhs))
148            }
149            (Inline::Float(lhs), Inline::Float(rhs)) => Ok(lhs.partial_cmp(rhs)),
150            (Inline::Type(lhs), Inline::Type(rhs)) => Ok(lhs.partial_cmp(rhs)),
151            (Inline::Ordering(lhs), Inline::Ordering(rhs)) => Ok(lhs.partial_cmp(rhs)),
152            (Inline::Hash(lhs), Inline::Hash(rhs)) => Ok(lhs.partial_cmp(rhs)),
153            (lhs, rhs) => Err(RuntimeError::from(
154                VmErrorKind::UnsupportedBinaryOperation {
155                    op: Protocol::PARTIAL_CMP.name,
156                    lhs: lhs.type_info(),
157                    rhs: rhs.type_info(),
158                },
159            )),
160        }
161    }
162
163    /// Total comparison implementation for inline.
164    pub(crate) fn cmp(&self, other: &Self) -> Result<Ordering, RuntimeError> {
165        match (self, other) {
166            (Inline::Unit, Inline::Unit) => Ok(Ordering::Equal),
167            (Inline::Bool(a), Inline::Bool(b)) => Ok(a.cmp(b)),
168            (Inline::Char(a), Inline::Char(b)) => Ok(a.cmp(b)),
169            (Inline::Unsigned(a), Inline::Unsigned(b)) => Ok(a.cmp(b)),
170            (Inline::Signed(a), Inline::Signed(b)) => Ok(a.cmp(b)),
171            (Inline::Float(a), Inline::Float(b)) => {
172                let Some(ordering) = a.partial_cmp(b) else {
173                    return Err(RuntimeError::new(VmErrorKind::IllegalFloatComparison {
174                        lhs: *a,
175                        rhs: *b,
176                    }));
177                };
178
179                Ok(ordering)
180            }
181            (Inline::Type(a), Inline::Type(b)) => Ok(a.cmp(b)),
182            (Inline::Ordering(a), Inline::Ordering(b)) => Ok(a.cmp(b)),
183            (Inline::Hash(a), Inline::Hash(b)) => Ok(a.cmp(b)),
184            (lhs, rhs) => Err(RuntimeError::new(VmErrorKind::UnsupportedBinaryOperation {
185                op: Protocol::CMP.name,
186                lhs: lhs.type_info(),
187                rhs: rhs.type_info(),
188            })),
189        }
190    }
191
192    /// Hash an inline value.
193    pub(crate) fn hash(&self, hasher: &mut Hasher) -> Result<(), RuntimeError> {
194        match self {
195            Inline::Unsigned(value) => {
196                value.hash(hasher);
197            }
198            Inline::Signed(value) => {
199                value.hash(hasher);
200            }
201            // Care must be taken whan hashing floats, to ensure that `hash(v1)
202            // === hash(v2)` if `eq(v1) === eq(v2)`. Hopefully we accomplish
203            // this by rejecting NaNs and rectifying subnormal values of zero.
204            Inline::Float(value) => {
205                if value.is_nan() {
206                    return Err(RuntimeError::new(VmErrorKind::IllegalFloatOperation {
207                        value: *value,
208                    }));
209                }
210
211                let zero = *value == 0.0;
212                let value = ((zero as u8 as f64) * 0.0 + (!zero as u8 as f64) * *value).to_bits();
213                value.hash(hasher);
214            }
215            operand => {
216                return Err(RuntimeError::new(VmErrorKind::UnsupportedUnaryOperation {
217                    op: Protocol::HASH.name,
218                    operand: operand.type_info(),
219                }));
220            }
221        }
222
223        Ok(())
224    }
225}
226
227impl fmt::Debug for Inline {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        match *self {
230            Inline::Empty => write!(f, "<empty>"),
231            Inline::Unit => write!(f, "()"),
232            Inline::Bool(value) => value.fmt(f),
233            Inline::Char(value) => value.fmt(f),
234            Inline::Unsigned(value) => value.fmt(f),
235            Inline::Signed(value) => value.fmt(f),
236            Inline::Float(value) => value.fmt(f),
237            Inline::Type(value) => value.fmt(f),
238            Inline::Ordering(value) => value.fmt(f),
239            Inline::Hash(value) => value.fmt(f),
240        }
241    }
242}
243
244impl Inline {
245    pub(crate) fn type_info(&self) -> TypeInfo {
246        match self {
247            Inline::Empty => TypeInfo::empty(),
248            Inline::Unit => TypeInfo::any::<OwnedTuple>(),
249            Inline::Bool(..) => TypeInfo::named::<bool>(),
250            Inline::Char(..) => TypeInfo::named::<char>(),
251            Inline::Unsigned(..) => TypeInfo::named::<u64>(),
252            Inline::Signed(..) => TypeInfo::named::<i64>(),
253            Inline::Float(..) => TypeInfo::named::<f64>(),
254            Inline::Type(..) => TypeInfo::named::<Type>(),
255            Inline::Ordering(..) => TypeInfo::named::<Ordering>(),
256            Inline::Hash(..) => TypeInfo::named::<Hash>(),
257        }
258    }
259
260    /// Get the type hash for the current value.
261    ///
262    /// One notable feature is that the type of a variant is its container
263    /// *enum*, and not the type hash of the variant itself.
264    pub(crate) fn type_hash(&self) -> Hash {
265        match self {
266            Inline::Empty => crate::hash!(::std::empty::Empty),
267            Inline::Unit => OwnedTuple::HASH,
268            Inline::Bool(..) => bool::HASH,
269            Inline::Char(..) => char::HASH,
270            Inline::Signed(..) => i64::HASH,
271            Inline::Unsigned(..) => u64::HASH,
272            Inline::Float(..) => f64::HASH,
273            Inline::Type(..) => Type::HASH,
274            Inline::Ordering(..) => Ordering::HASH,
275            Inline::Hash(..) => Hash::HASH,
276        }
277    }
278}