rune/runtime/value/
inline.rs

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