wasmtime_wast/
component.rs

1use crate::core;
2use anyhow::{bail, Context, Result};
3use std::collections::BTreeSet;
4use std::fmt::Debug;
5use wast::component::WastVal;
6use wast::core::NanPattern;
7
8pub use wasmtime::component::*;
9
10pub fn val(v: &WastVal<'_>) -> Result<Val> {
11    Ok(match v {
12        WastVal::Bool(b) => Val::Bool(*b),
13        WastVal::U8(b) => Val::U8(*b),
14        WastVal::S8(b) => Val::S8(*b),
15        WastVal::U16(b) => Val::U16(*b),
16        WastVal::S16(b) => Val::S16(*b),
17        WastVal::U32(b) => Val::U32(*b),
18        WastVal::S32(b) => Val::S32(*b),
19        WastVal::U64(b) => Val::U64(*b),
20        WastVal::S64(b) => Val::S64(*b),
21        WastVal::F32(b) => Val::Float32(f32::from_bits(b.bits)),
22        WastVal::F64(b) => Val::Float64(f64::from_bits(b.bits)),
23        WastVal::Char(b) => Val::Char(*b),
24        WastVal::String(s) => Val::String(s.to_string().into()),
25        WastVal::List(vals) => {
26            let vals = vals.iter().map(|v| val(v)).collect::<Result<Vec<_>>>()?;
27            Val::List(vals.into())
28        }
29        WastVal::Record(vals) => {
30            let mut fields = Vec::new();
31            for (name, v) in vals {
32                fields.push((name.to_string(), val(v)?));
33            }
34            Val::Record(fields.into())
35        }
36        WastVal::Tuple(vals) => Val::Tuple(
37            vals.iter()
38                .map(|v| val(v))
39                .collect::<Result<Vec<_>>>()?
40                .into(),
41        ),
42        WastVal::Enum(name) => Val::Enum(name.to_string()),
43        WastVal::Variant(name, payload) => {
44            let payload = payload_val(payload.as_deref())?;
45            Val::Variant(name.to_string(), payload)
46        }
47        WastVal::Option(v) => Val::Option(match v {
48            Some(v) => Some(Box::new(val(v)?)),
49            None => None,
50        }),
51        WastVal::Result(v) => Val::Result(match v {
52            Ok(v) => Ok(payload_val(v.as_deref())?),
53            Err(v) => Err(payload_val(v.as_deref())?),
54        }),
55        WastVal::Flags(v) => Val::Flags(v.iter().map(|s| s.to_string()).collect()),
56    })
57}
58
59fn payload_val(v: Option<&WastVal<'_>>) -> Result<Option<Box<Val>>> {
60    match v {
61        Some(v) => Ok(Some(Box::new(val(v)?))),
62        None => Ok(None),
63    }
64}
65
66pub fn match_val(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
67    match expected {
68        WastVal::Bool(e) => match actual {
69            Val::Bool(a) => match_debug(a, e),
70            _ => mismatch(expected, actual),
71        },
72        WastVal::U8(e) => match actual {
73            Val::U8(a) => core::match_int(a, e),
74            _ => mismatch(expected, actual),
75        },
76        WastVal::S8(e) => match actual {
77            Val::S8(a) => core::match_int(a, e),
78            _ => mismatch(expected, actual),
79        },
80        WastVal::U16(e) => match actual {
81            Val::U16(a) => core::match_int(a, e),
82            _ => mismatch(expected, actual),
83        },
84        WastVal::S16(e) => match actual {
85            Val::S16(a) => core::match_int(a, e),
86            _ => mismatch(expected, actual),
87        },
88        WastVal::U32(e) => match actual {
89            Val::U32(a) => core::match_int(a, e),
90            _ => mismatch(expected, actual),
91        },
92        WastVal::S32(e) => match actual {
93            Val::S32(a) => core::match_int(a, e),
94            _ => mismatch(expected, actual),
95        },
96        WastVal::U64(e) => match actual {
97            Val::U64(a) => core::match_int(a, e),
98            _ => mismatch(expected, actual),
99        },
100        WastVal::S64(e) => match actual {
101            Val::S64(a) => core::match_int(a, e),
102            _ => mismatch(expected, actual),
103        },
104        WastVal::F32(e) => match actual {
105            Val::Float32(a) => core::match_f32(a.to_bits(), &NanPattern::Value(*e)),
106            _ => mismatch(expected, actual),
107        },
108        WastVal::F64(e) => match actual {
109            Val::Float64(a) => core::match_f64(a.to_bits(), &NanPattern::Value(*e)),
110            _ => mismatch(expected, actual),
111        },
112        WastVal::Char(e) => match actual {
113            Val::Char(a) => match_debug(a, e),
114            _ => mismatch(expected, actual),
115        },
116        WastVal::String(e) => match actual {
117            Val::String(a) => match_debug(&a[..], *e),
118            _ => mismatch(expected, actual),
119        },
120        WastVal::List(e) => match actual {
121            Val::List(a) => {
122                if e.len() != a.len() {
123                    bail!("expected {} values got {}", e.len(), a.len());
124                }
125                for (i, (expected, actual)) in e.iter().zip(a.iter()).enumerate() {
126                    match_val(expected, actual)
127                        .with_context(|| format!("failed to match list element {i}"))?;
128                }
129                Ok(())
130            }
131            _ => mismatch(expected, actual),
132        },
133        WastVal::Record(e) => match actual {
134            Val::Record(a) => {
135                if e.len() != e.len() {
136                    bail!("mismatched number of record fields");
137                }
138                for ((e_name, e_val), (a_name, a_val)) in e.iter().zip(a.iter()) {
139                    if e_name != a_name {
140                        bail!("expected field `{e_name}` got `{a_name}`");
141                    }
142                    match_val(e_val, a_val)
143                        .with_context(|| format!("failed to match field `{e_name}`"))?;
144                }
145                Ok(())
146            }
147            _ => mismatch(expected, actual),
148        },
149        WastVal::Tuple(e) => match actual {
150            Val::Tuple(a) => {
151                if e.len() != a.len() {
152                    bail!("expected {}-tuple, found {}-tuple", e.len(), a.len());
153                }
154                for (i, (expected, actual)) in e.iter().zip(a.iter()).enumerate() {
155                    match_val(expected, actual)
156                        .with_context(|| format!("failed to match tuple element {i}"))?;
157                }
158                Ok(())
159            }
160            _ => mismatch(expected, actual),
161        },
162        WastVal::Variant(name, e) => match actual {
163            Val::Variant(discr, payload) => {
164                if *discr != *name {
165                    bail!("expected discriminant `{name}` got `{discr}`");
166                }
167                match_payload_val(name, e.as_deref(), payload.as_deref())
168            }
169            _ => mismatch(expected, actual),
170        },
171        WastVal::Enum(name) => match actual {
172            Val::Enum(a) => {
173                if *a != *name {
174                    bail!("expected discriminant `{name}` got `{a}`");
175                } else {
176                    Ok(())
177                }
178            }
179            _ => mismatch(expected, actual),
180        },
181        WastVal::Option(e) => match actual {
182            Val::Option(a) => match (e, a) {
183                (None, None) => Ok(()),
184                (Some(expected), Some(actual)) => match_val(expected, actual),
185                (None, Some(_)) => bail!("expected `none`, found `some`"),
186                (Some(_), None) => bail!("expected `some`, found `none`"),
187            },
188            _ => mismatch(expected, actual),
189        },
190        WastVal::Result(e) => match actual {
191            Val::Result(a) => match (e, a) {
192                (Ok(_), Err(_)) => bail!("expected `ok`, found `err`"),
193                (Err(_), Ok(_)) => bail!("expected `err`, found `ok`"),
194                (Err(e), Err(a)) => match_payload_val("err", e.as_deref(), a.as_deref()),
195                (Ok(e), Ok(a)) => match_payload_val("ok", e.as_deref(), a.as_deref()),
196            },
197            _ => mismatch(expected, actual),
198        },
199        WastVal::Flags(e) => match actual {
200            Val::Flags(a) => {
201                let expected = e.iter().copied().collect::<BTreeSet<_>>();
202                let actual = a.iter().map(|s| s.as_str()).collect::<BTreeSet<_>>();
203                match_debug(&actual, &expected)
204            }
205            _ => mismatch(expected, actual),
206        },
207    }
208}
209
210fn match_payload_val(
211    name: &str,
212    expected: Option<&WastVal<'_>>,
213    actual: Option<&Val>,
214) -> Result<()> {
215    match (expected, actual) {
216        (Some(e), Some(a)) => {
217            match_val(e, a).with_context(|| format!("failed to match case `{name}`"))
218        }
219        (None, None) => Ok(()),
220        (Some(_), None) => bail!("expected payload for case `{name}`"),
221        (None, Some(_)) => bail!("unexpected payload for case `{name}`"),
222    }
223}
224
225fn match_debug<T>(actual: &T, expected: &T) -> Result<()>
226where
227    T: Eq + Debug + ?Sized,
228{
229    if actual == expected {
230        Ok(())
231    } else {
232        bail!(
233            "
234             expected {expected:?}
235             actual   {actual:?}"
236        )
237    }
238}
239
240fn mismatch(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
241    let expected = match expected {
242        WastVal::Bool(..) => "bool",
243        WastVal::U8(..) => "u8",
244        WastVal::S8(..) => "s8",
245        WastVal::U16(..) => "u16",
246        WastVal::S16(..) => "s16",
247        WastVal::U32(..) => "u32",
248        WastVal::S32(..) => "s32",
249        WastVal::U64(..) => "u64",
250        WastVal::S64(..) => "s64",
251        WastVal::F32(..) => "f32",
252        WastVal::F64(..) => "f64",
253        WastVal::Char(..) => "char",
254        WastVal::String(..) => "string",
255        WastVal::List(..) => "list",
256        WastVal::Record(..) => "record",
257        WastVal::Tuple(..) => "tuple",
258        WastVal::Enum(..) => "enum",
259        WastVal::Variant(..) => "variant",
260        WastVal::Option(..) => "option",
261        WastVal::Result(..) => "result",
262        WastVal::Flags(..) => "flags",
263    };
264    let actual = match actual {
265        Val::Bool(..) => "bool",
266        Val::U8(..) => "u8",
267        Val::S8(..) => "s8",
268        Val::U16(..) => "u16",
269        Val::S16(..) => "s16",
270        Val::U32(..) => "u32",
271        Val::S32(..) => "s32",
272        Val::U64(..) => "u64",
273        Val::S64(..) => "s64",
274        Val::Float32(..) => "f32",
275        Val::Float64(..) => "f64",
276        Val::Char(..) => "char",
277        Val::String(..) => "string",
278        Val::List(..) => "list",
279        Val::Record(..) => "record",
280        Val::Tuple(..) => "tuple",
281        Val::Enum(..) => "enum",
282        Val::Variant(..) => "variant",
283        Val::Option(..) => "option",
284        Val::Result(..) => "result",
285        Val::Flags(..) => "flags",
286        Val::Resource(..) => "resource",
287    };
288    bail!("expected `{expected}` got `{actual}`")
289}