rib/interpreter/
literal.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Golem Source License v1.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://license.golem.cloud/LICENSE
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use golem_wasm_ast::analysis::AnalysedType;
16use golem_wasm_rpc::{IntoValueAndType, Value, ValueAndType};
17use std::cmp::Ordering;
18use std::fmt::Display;
19
20pub trait GetLiteralValue {
21    fn get_literal(&self) -> Option<LiteralValue>;
22}
23
24impl GetLiteralValue for ValueAndType {
25    fn get_literal(&self) -> Option<LiteralValue> {
26        match self {
27            ValueAndType {
28                value: Value::String(value),
29                ..
30            } => Some(LiteralValue::String(value.clone())),
31            ValueAndType {
32                value: Value::Char(code_point),
33                ..
34            } => char::from_u32(*code_point as u32)
35                .map(|c| c.to_string())
36                .map(LiteralValue::String),
37            ValueAndType {
38                value: Value::Bool(value),
39                ..
40            } => Some(LiteralValue::Bool(*value)),
41            ValueAndType {
42                value: Value::Enum(idx),
43                typ: AnalysedType::Enum(typ),
44            } => {
45                // An enum can be turned into a simple literal and can be part of string concatenations
46                Some(LiteralValue::String(typ.cases[*idx as usize].clone()))
47            }
48            ValueAndType {
49                value:
50                    Value::Variant {
51                        case_idx,
52                        case_value,
53                    },
54                typ: AnalysedType::Variant(typ),
55            } => {
56                // A no arg variant can be turned into a simple literal and can be part of string concatenations
57                if case_value.is_none() {
58                    Some(LiteralValue::String(
59                        typ.cases[*case_idx as usize].name.clone(),
60                    ))
61                } else {
62                    None
63                }
64            }
65            other => internal::get_numeric_value(other).map(LiteralValue::Num),
66        }
67    }
68}
69
70#[derive(Clone, Debug, PartialEq, PartialOrd)]
71pub enum LiteralValue {
72    Num(CoercedNumericValue),
73    String(String),
74    Bool(bool),
75}
76
77impl LiteralValue {
78    pub fn get_bool(&self) -> Option<bool> {
79        match self {
80            LiteralValue::Bool(value) => Some(*value),
81            _ => None,
82        }
83    }
84
85    pub fn get_number(&self) -> Option<CoercedNumericValue> {
86        match self {
87            LiteralValue::Num(num) => Some(num.clone()),
88            _ => None,
89        }
90    }
91
92    pub fn as_string(&self) -> String {
93        match self {
94            LiteralValue::Num(number) => number.to_string(),
95            LiteralValue::String(value) => value.clone(),
96            LiteralValue::Bool(value) => value.to_string(),
97        }
98    }
99}
100
101impl From<String> for LiteralValue {
102    fn from(value: String) -> Self {
103        if let Ok(u64) = value.parse::<u64>() {
104            LiteralValue::Num(CoercedNumericValue::PosInt(u64))
105        } else if let Ok(i64_value) = value.parse::<i64>() {
106            LiteralValue::Num(CoercedNumericValue::NegInt(i64_value))
107        } else if let Ok(f64_value) = value.parse::<f64>() {
108            LiteralValue::Num(CoercedNumericValue::Float(f64_value))
109        } else if let Ok(bool) = value.parse::<bool>() {
110            LiteralValue::Bool(bool)
111        } else {
112            LiteralValue::String(value.to_string())
113        }
114    }
115}
116
117// A coerced representation of numeric wasm types, simplifying finer-grained TypeAnnotatedValue types into u64, i64, and f64.
118#[derive(Clone, Debug)]
119pub enum CoercedNumericValue {
120    PosInt(u64),
121    NegInt(i64),
122    Float(f64),
123}
124
125impl CoercedNumericValue {
126    pub fn is_zero(&self) -> bool {
127        match self {
128            CoercedNumericValue::PosInt(val) => *val == 0,
129            CoercedNumericValue::NegInt(val) => *val == 0,
130            CoercedNumericValue::Float(val) => *val == 0.0,
131        }
132    }
133
134    pub fn cast_to(&self, analysed_type: &AnalysedType) -> Option<ValueAndType> {
135        match self {
136            CoercedNumericValue::PosInt(number) => {
137                let num = *number;
138
139                match analysed_type {
140                    AnalysedType::U8(_) if num <= u8::MAX as u64 => {
141                        Some((num as u8).into_value_and_type())
142                    }
143                    AnalysedType::U16(_) if num <= u16::MAX as u64 => {
144                        Some((num as u16).into_value_and_type())
145                    }
146                    AnalysedType::U32(_) if num <= u32::MAX as u64 => {
147                        Some((num as u32).into_value_and_type())
148                    }
149                    AnalysedType::U64(_) => Some(num.into_value_and_type()),
150
151                    AnalysedType::S8(_) if num <= i8::MAX as u64 => {
152                        Some((num as i8).into_value_and_type())
153                    }
154                    AnalysedType::S16(_) if num <= i16::MAX as u64 => {
155                        Some((num as i16).into_value_and_type())
156                    }
157                    AnalysedType::S32(_) if num <= i32::MAX as u64 => {
158                        Some((num as i32).into_value_and_type())
159                    }
160                    AnalysedType::S64(_) if num <= i64::MAX as u64 => {
161                        Some((num as i64).into_value_and_type())
162                    }
163
164                    AnalysedType::F32(_) if num <= f32::MAX as u64 => {
165                        Some((num as f32).into_value_and_type())
166                    }
167                    AnalysedType::F64(_) if num <= f64::MAX as u64 => {
168                        Some((num as f64).into_value_and_type())
169                    }
170
171                    _ => None,
172                }
173            }
174
175            CoercedNumericValue::NegInt(number) => {
176                let num = *number;
177
178                match analysed_type {
179                    AnalysedType::S8(_) if num >= i8::MIN as i64 && num <= i8::MAX as i64 => {
180                        Some((num as i8).into_value_and_type())
181                    }
182                    AnalysedType::S16(_) if num >= i16::MIN as i64 && num <= i16::MAX as i64 => {
183                        Some((num as i16).into_value_and_type())
184                    }
185                    AnalysedType::S32(_) if num >= i32::MIN as i64 && num <= i32::MAX as i64 => {
186                        Some((num as i32).into_value_and_type())
187                    }
188                    AnalysedType::S64(_) => Some(num.into_value_and_type()),
189
190                    // Allow unsigned conversion only if non-negative
191                    AnalysedType::U8(_) if num >= 0 && num <= u8::MAX as i64 => {
192                        Some((num as u8).into_value_and_type())
193                    }
194                    AnalysedType::U16(_) if num >= 0 && num <= u16::MAX as i64 => {
195                        Some((num as u16).into_value_and_type())
196                    }
197                    AnalysedType::U32(_) if num >= 0 && num <= u32::MAX as i64 => {
198                        Some((num as u32).into_value_and_type())
199                    }
200                    AnalysedType::U64(_) if num >= 0 => Some((num as u64).into_value_and_type()),
201
202                    AnalysedType::F32(_) if num >= f32::MIN as i64 && num <= f32::MAX as i64 => {
203                        Some((num as f32).into_value_and_type())
204                    }
205                    AnalysedType::F64(_) if num >= f64::MIN as i64 && num <= f64::MAX as i64 => {
206                        Some((num as f64).into_value_and_type())
207                    }
208
209                    _ => None,
210                }
211            }
212
213            CoercedNumericValue::Float(number) => {
214                let num = *number;
215
216                match analysed_type {
217                    AnalysedType::F64(_) => Some(num.into_value_and_type()),
218
219                    AnalysedType::F32(_)
220                        if num.is_finite() && num >= f32::MIN as f64 && num <= f32::MAX as f64 =>
221                    {
222                        Some((num as f32).into_value_and_type())
223                    }
224
225                    AnalysedType::U64(_)
226                        if num.is_finite()
227                            && num >= 0.0
228                            && num <= u64::MAX as f64
229                            && num.fract() == 0.0 =>
230                    {
231                        Some((num as u64).into_value_and_type())
232                    }
233
234                    AnalysedType::S64(_)
235                        if num.is_finite()
236                            && num >= i64::MIN as f64
237                            && num <= i64::MAX as f64
238                            && num.fract() == 0.0 =>
239                    {
240                        Some((num as i64).into_value_and_type())
241                    }
242
243                    _ => None,
244                }
245            }
246        }
247    }
248}
249
250macro_rules! impl_ops {
251    ($trait:ident, $method:ident, $checked_method:ident) => {
252        impl std::ops::$trait for CoercedNumericValue {
253            type Output = Result<Self, String>;
254
255            fn $method(self, rhs: Self) -> Self::Output {
256                use CoercedNumericValue::*;
257                Ok(match (self, rhs) {
258                    (Float(a), Float(b)) => Float(a.$method(b)),
259                    (Float(a), PosInt(b)) => Float(a.$method(b as f64)),
260                    (Float(a), NegInt(b)) => Float(a.$method(b as f64)),
261                    (PosInt(a), Float(b)) => Float((a as f64).$method(b)),
262                    (NegInt(a), Float(b)) => Float((a as f64).$method(b)),
263                    (PosInt(a), PosInt(b)) => a.$checked_method(b).map(PosInt).ok_or(format!(
264                        "overflow in unsigned operation between {} and {}",
265                        a, b
266                    ))?,
267                    (NegInt(a), NegInt(b)) => a.$checked_method(b).map(NegInt).ok_or(format!(
268                        "overflow in signed operation between {} and {}",
269                        a, b
270                    ))?,
271                    (PosInt(a), NegInt(b)) => (a as i64).$checked_method(b).map(NegInt).ok_or(
272                        format!("overflow in signed operation between {} and {}", a, b),
273                    )?,
274                    (NegInt(a), PosInt(b)) => a.$checked_method(b as i64).map(NegInt).ok_or(
275                        format!("overflow in signed operation between {} and {}", a, b),
276                    )?,
277                })
278            }
279        }
280    };
281}
282
283impl_ops!(Add, add, checked_add);
284impl_ops!(Sub, sub, checked_sub);
285impl_ops!(Mul, mul, checked_mul);
286impl_ops!(Div, div, checked_div);
287
288// Auto-derived PartialOrd fails if types don't match
289// and therefore custom impl.
290impl PartialOrd for CoercedNumericValue {
291    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
292        use CoercedNumericValue::*;
293        match (self, other) {
294            (PosInt(a), PosInt(b)) => a.partial_cmp(b),
295            (NegInt(a), NegInt(b)) => a.partial_cmp(b),
296            (Float(a), Float(b)) => a.partial_cmp(b),
297
298            (PosInt(a), NegInt(b)) => {
299                if let Ok(b_as_u64) = u64::try_from(*b) {
300                    a.partial_cmp(&b_as_u64)
301                } else {
302                    Some(Ordering::Greater) // Positive numbers are greater than negative numbers
303                }
304            }
305
306            (NegInt(a), PosInt(b)) => {
307                if let Ok(a_as_u64) = u64::try_from(*a) {
308                    a_as_u64.partial_cmp(b)
309                } else {
310                    Some(Ordering::Less) // Negative numbers are less than positive numbers
311                }
312            }
313
314            (PosInt(a), Float(b)) => (*a as f64).partial_cmp(b),
315
316            (Float(a), PosInt(b)) => a.partial_cmp(&(*b as f64)),
317
318            (NegInt(a), Float(b)) => (*a as f64).partial_cmp(b),
319
320            (Float(a), NegInt(b)) => a.partial_cmp(&(*b as f64)),
321        }
322    }
323}
324
325// Similarly, auto-derived PartialEq fails if types don't match
326// and therefore custom impl
327// There is a high chance two variables can be inferred S32(1) and U32(1)
328impl PartialEq for CoercedNumericValue {
329    fn eq(&self, other: &Self) -> bool {
330        use CoercedNumericValue::*;
331        match (self, other) {
332            (PosInt(a), PosInt(b)) => a == b,
333            (NegInt(a), NegInt(b)) => a == b,
334            (Float(a), Float(b)) => a == b,
335
336            // Comparing PosInt with NegInt
337            (PosInt(a), NegInt(b)) => {
338                if let Ok(b_as_u64) = u64::try_from(*b) {
339                    a == &b_as_u64
340                } else {
341                    false
342                }
343            }
344
345            // Comparing NegInt with PosInt
346            (NegInt(a), PosInt(b)) => {
347                if let Ok(a_as_u64) = u64::try_from(*a) {
348                    &a_as_u64 == b
349                } else {
350                    false
351                }
352            }
353
354            // Comparing PosInt with Float
355            (PosInt(a), Float(b)) => (*a as f64) == *b,
356
357            // Comparing Float with PosInt
358            (Float(a), PosInt(b)) => *a == (*b as f64),
359
360            // Comparing NegInt with Float
361            (NegInt(a), Float(b)) => (*a as f64) == *b,
362
363            // Comparing Float with NegInt
364            (Float(a), NegInt(b)) => *a == (*b as f64),
365        }
366    }
367}
368
369impl Display for CoercedNumericValue {
370    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371        match self {
372            CoercedNumericValue::PosInt(value) => write!(f, "{value}"),
373            CoercedNumericValue::NegInt(value) => write!(f, "{value}"),
374            CoercedNumericValue::Float(value) => write!(f, "{value}"),
375        }
376    }
377}
378
379impl Display for LiteralValue {
380    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381        match self {
382            LiteralValue::Num(number) => write!(f, "{number}"),
383            LiteralValue::String(value) => write!(f, "{value}"),
384            LiteralValue::Bool(value) => write!(f, "{value}"),
385        }
386    }
387}
388
389mod internal {
390    use crate::interpreter::literal::CoercedNumericValue;
391    use golem_wasm_rpc::{Value, ValueAndType};
392
393    pub(crate) fn get_numeric_value(value_and_type: &ValueAndType) -> Option<CoercedNumericValue> {
394        match &value_and_type.value {
395            Value::S8(value) => Some(CoercedNumericValue::NegInt(*value as i64)),
396            Value::S16(value) => Some(CoercedNumericValue::NegInt(*value as i64)),
397            Value::S32(value) => Some(CoercedNumericValue::NegInt(*value as i64)),
398            Value::S64(value) => Some(CoercedNumericValue::NegInt(*value)),
399            Value::U8(value) => Some(CoercedNumericValue::PosInt(*value as u64)),
400            Value::U16(value) => Some(CoercedNumericValue::PosInt(*value as u64)),
401            Value::U32(value) => Some(CoercedNumericValue::PosInt(*value as u64)),
402            Value::U64(value) => Some(CoercedNumericValue::PosInt(*value)),
403            Value::F32(value) => Some(CoercedNumericValue::Float(*value as f64)),
404            Value::F64(value) => Some(CoercedNumericValue::Float(*value)),
405            _ => None,
406        }
407    }
408}