diff --git a/Cargo.lock b/Cargo.lock index 265a73f085..d2d122f76c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1345,11 +1345,22 @@ name = "gen_wasm" version = "0.1.0" dependencies = [ "bumpalo", + "indoc 0.3.6", + "libc", "parity-wasm", + "pretty_assertions 0.5.1", + "roc_builtins", + "roc_can", "roc_collections", + "roc_load", "roc_module", "roc_mono", + "roc_std", + "roc_types", + "target-lexicon", + "tempfile", "wasmer", + "wasmer-wasi", ] [[package]] diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index 15c2140934..14e2be583d 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -13,4 +13,16 @@ bumpalo = { version = "3.6.1", features = ["collections"] } parity-wasm = "0.42" [dev-dependencies] +roc_can = { path = "../can" } +roc_builtins = { path = "../builtins" } +roc_load = { path = "../load" } +roc_types = { path = "../types" } +roc_std = { path = "../../roc_std" } +roc_module = { path = "../module" } +indoc = "0.3.3" +pretty_assertions = "0.5.1" +libc = "0.2" +target-lexicon = "0.12.2" wasmer = "2.0.0" +wasmer-wasi = "2.0.0" +tempfile = "3.1.0" diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs new file mode 100644 index 0000000000..b11eda739a --- /dev/null +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -0,0 +1,469 @@ +use roc_can::builtins::builtin_defs_map; +use roc_collections::all::{MutMap, MutSet}; +use roc_std::{RocDec, RocList, RocOrder, RocStr}; + +fn promote_expr_to_module(src: &str) -> String { + let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); + + for line in src.lines() { + // indent the body! + buffer.push_str(" "); + buffer.push_str(line); + buffer.push('\n'); + } + + buffer +} + +#[allow(dead_code)] +pub fn helper_wasm<'a>( + arena: &'a bumpalo::Bump, + src: &str, + stdlib: &'a roc_builtins::std::StdLib, + _is_gen_test: bool, + _ignore_problems: bool, +) -> wasmer::Instance { + use std::path::{Path, PathBuf}; + + let filename = PathBuf::from("Test.roc"); + let src_dir = Path::new("fake/test/path"); + + let module_src; + let temp; + if src.starts_with("app") { + // this is already a module + module_src = src; + } else { + // this is an expression, promote it to a module + temp = promote_expr_to_module(src); + module_src = &temp; + } + + let exposed_types = MutMap::default(); + let loaded = roc_load::file::load_and_monomorphize_from_str( + arena, + filename, + module_src, + stdlib, + src_dir, + exposed_types, + 8, + builtin_defs_map, + ); + + let loaded = loaded.expect("failed to load module"); + + use roc_load::file::MonomorphizedModule; + let MonomorphizedModule { + procedures: top_procedures, + interns, + exposed_to_host, + .. + } = loaded; + + let mut procedures = MutMap::default(); + + for (key, proc) in top_procedures { + procedures.insert(key, proc); + } + + let exposed_to_host = exposed_to_host.keys().copied().collect::>(); + + let env = gen_wasm::Env { + arena, + interns, + exposed_to_host, + }; + + let module_bytes = gen_wasm::build_module(&env, procedures).unwrap(); + + // for debugging (e.g. with wasm2wat) + if false { + use std::io::Write; + let mut file = std::fs::File::create("/home/folkertdev/roc/wasm/manual.wasm").unwrap(); + file.write_all(&module_bytes).unwrap(); + } + + // now, do wasmer stuff + + use wasmer::{Function, Instance, Module, Store}; + + let store = Store::default(); + // let module = Module::from_file(&store, &test_wasm_path).unwrap(); + let module = Module::new(&store, &module_bytes).unwrap(); + + // First, we create the `WasiEnv` + use wasmer_wasi::WasiState; + let mut wasi_env = WasiState::new("hello") + // .args(&["world"]) + // .env("KEY", "Value") + .finalize() + .unwrap(); + + // Then, we get the import object related to our WASI + // and attach it to the Wasm instance. + let mut import_object = wasi_env + .import_object(&module) + .unwrap_or_else(|_| wasmer::imports!()); + + { + let mut exts = wasmer::Exports::new(); + + let main_function = Function::new_native(&store, fake_wasm_main_function); + let ext = wasmer::Extern::Function(main_function); + exts.insert("main", ext); + + let main_function = Function::new_native(&store, wasm_roc_panic); + let ext = wasmer::Extern::Function(main_function); + exts.insert("roc_panic", ext); + + import_object.register("env", exts); + } + + Instance::new(&module, &import_object).unwrap() +} + +#[allow(dead_code)] +fn wasm_roc_panic(address: u32, tag_id: u32) { + match tag_id { + 0 => { + let mut string = ""; + + MEMORY.with(|f| { + let memory = f.borrow().unwrap(); + + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(address); + let width = 100; + let c_ptr = (ptr.deref(memory, 0, width)).unwrap(); + + use libc::c_char; + use std::ffi::CStr; + let slice = unsafe { CStr::from_ptr(c_ptr as *const _ as *const c_char) }; + string = slice.to_str().unwrap(); + }); + + panic!("Roc failed with message: {:?}", string) + } + _ => todo!(), + } +} + +use std::cell::RefCell; + +thread_local! { + pub static MEMORY: RefCell> = RefCell::new(None); +} + +#[allow(dead_code)] +fn fake_wasm_main_function(_: u32, _: u32) -> u32 { + panic!("wasm entered the main function; this should never happen!") +} + +#[allow(dead_code)] +pub fn assert_wasm_evals_to_help(src: &str, ignore_problems: bool) -> Result +where + T: FromWasmMemory, +{ + let arena = bumpalo::Bump::new(); + + // NOTE the stdlib must be in the arena; just taking a reference will segfault + let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); + + let is_gen_test = true; + let instance = + crate::helpers::eval::helper_wasm(&arena, src, stdlib, is_gen_test, ignore_problems); + + let memory = instance.exports.get_memory("memory").unwrap(); + + crate::helpers::eval::MEMORY.with(|f| { + *f.borrow_mut() = Some(unsafe { std::mem::transmute(memory) }); + }); + + let test_wrapper = instance.exports.get_function("test_wrapper").unwrap(); + + match test_wrapper.call(&[]) { + Err(e) => Err(format!("{:?}", e)), + Ok(result) => { + let address = match result[0] { + wasmer::Value::I32(a) => a, + _ => panic!(), + }; + + let output = ::decode( + memory, + // skip the RocCallResult tag id + address as u32 + 8, + ); + + Ok(output) + } + } +} + +#[macro_export] +macro_rules! assert_wasm_evals_to { + ($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => { + match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $ignore_problems) { + Err(msg) => println!("{:?}", msg), + Ok(actual) => { + #[allow(clippy::bool_assert_comparison)] + assert_eq!($transform(actual), $expected) + } + } + }; + + ($src:expr, $expected:expr, $ty:ty) => { + $crate::assert_wasm_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity, false); + }; + + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + $crate::assert_wasm_evals_to!($src, $expected, $ty, $transform, false); + }; +} + +#[macro_export] +macro_rules! assert_evals_to { + ($src:expr, $expected:expr, $ty:ty) => {{ + assert_evals_to!($src, $expected, $ty, $crate::helpers::eval::identity); + }}; + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + // Same as above, except with an additional transformation argument. + { + $crate::assert_wasm_evals_to!($src, $expected, $ty, $transform, false); + } + }; +} + +#[allow(dead_code)] +pub fn identity(value: T) -> T { + value +} + +pub trait FromWasmMemory: Sized { + const SIZE_OF_WASM: usize; + const ALIGN_OF_WASM: usize; + const ACTUAL_WIDTH: usize = if (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM) == 0 { + Self::SIZE_OF_WASM + } else { + Self::SIZE_OF_WASM + (Self::ALIGN_OF_WASM - (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM)) + }; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self; +} + +macro_rules! from_wasm_memory_primitive_decode { + ($type_name:ident) => { + const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>(); + const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>(); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + use core::mem::MaybeUninit; + + let mut output: MaybeUninit = MaybeUninit::uninit(); + let width = std::mem::size_of::(); + + let ptr = output.as_mut_ptr(); + let raw_ptr = ptr as *mut u8; + let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) }; + + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset as u32); + let foobar = (ptr.deref(memory, 0, width as u32)).unwrap(); + let wasm_slice = unsafe { std::mem::transmute(foobar) }; + + slice.copy_from_slice(wasm_slice); + + unsafe { output.assume_init() } + } + }; +} + +macro_rules! from_wasm_memory_primitive { + ($($type_name:ident ,)+) => { + $( + impl FromWasmMemory for $type_name { + from_wasm_memory_primitive_decode!($type_name); + } + )* + } +} + +from_wasm_memory_primitive!( + u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder, +); + +impl FromWasmMemory for () { + const SIZE_OF_WASM: usize = 0; + const ALIGN_OF_WASM: usize = 0; + + fn decode(_: &wasmer::Memory, _: u32) -> Self {} +} + +impl FromWasmMemory for RocStr { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + if length == 0 { + RocStr::default() + } else if (length as i32) < 0 { + // this is a small string + let last_byte = bytes.to_ne_bytes()[7]; + let actual_length = (last_byte ^ 0b1000_0000) as usize; + + let slice = &bytes.to_ne_bytes()[..actual_length as usize]; + RocStr::from_slice(slice) + } else { + // this is a big string + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); + let foobar = (ptr.deref(memory, 0, length)).unwrap(); + let wasm_slice = unsafe { std::mem::transmute(foobar) }; + + RocStr::from_slice(wasm_slice) + } + } +} + +impl FromWasmMemory for RocList { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + let mut items = Vec::with_capacity(length as usize); + + for i in 0..length { + let item = ::decode( + memory, + elements + i * ::SIZE_OF_WASM as u32, + ); + items.push(item); + } + + RocList::from_slice(&items) + } +} + +impl FromWasmMemory for &'_ [T] { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); + let width = ::SIZE_OF_WASM as u32 * length; + let foobar = (ptr.deref(memory, 0, width)).unwrap(); + let wasm_slice = + unsafe { std::slice::from_raw_parts(foobar as *const _ as *const _, length as usize) }; + + wasm_slice + } +} + +impl FromWasmMemory for &'_ T { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let elements = ::decode(memory, offset); + + let actual = ::decode(memory, elements); + + let b = Box::new(actual); + + std::boxed::Box::::leak(b) + } +} + +impl FromWasmMemory for [T; N] { + const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset); + let width = ::SIZE_OF_WASM as u32 * N as u32; + let foobar = (ptr.deref(memory, 0, width)).unwrap(); + let wasm_slice: &[T; N] = unsafe { &*(foobar as *const _ as *const [T; N]) }; + + wasm_slice.clone() + } +} + +impl FromWasmMemory for usize { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + ::decode(memory, offset) as usize + } +} + +impl FromWasmMemory for (T, U) { + const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = max2(T::SIZE_OF_WASM, U::SIZE_OF_WASM); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + assert!( + T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + let t = ::decode(memory, offset); + + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + + (t, u) + } +} + +const fn max2(a: usize, b: usize) -> usize { + if a > b { + a + } else { + b + } +} + +const fn max3(a: usize, b: usize, c: usize) -> usize { + max2(max2(a, b), c) +} + +impl FromWasmMemory for (T, U, V) { + const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = max3(T::SIZE_OF_WASM, U::SIZE_OF_WASM, V::SIZE_OF_WASM); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + assert!( + T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + assert!( + U::ALIGN_OF_WASM >= V::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + let t = ::decode(memory, offset); + + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + + let v = ::decode( + memory, + offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32, + ); + + (t, u, v) + } +} diff --git a/compiler/gen_wasm/tests/helpers/mod.rs b/compiler/gen_wasm/tests/helpers/mod.rs new file mode 100644 index 0000000000..7e538193f7 --- /dev/null +++ b/compiler/gen_wasm/tests/helpers/mod.rs @@ -0,0 +1,44 @@ +extern crate bumpalo; + +#[macro_use] +pub mod eval; + +/// Used in the with_larger_debug_stack() function, for tests that otherwise +/// run out of stack space in debug builds (but don't in --release builds) +#[allow(dead_code)] +const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024; + +/// Without this, some tests pass in `cargo test --release` but fail without +/// the --release flag because they run out of stack space. This increases +/// stack size for debug builds only, while leaving the stack space at the default +/// amount for release builds. +#[allow(dead_code)] +#[cfg(debug_assertions)] +pub fn with_larger_debug_stack(run_test: F) +where + F: FnOnce(), + F: Send, + F: 'static, +{ + std::thread::Builder::new() + .stack_size(EXPANDED_STACK_SIZE) + .spawn(run_test) + .expect("Error while spawning expanded dev stack size thread") + .join() + .expect("Error while joining expanded dev stack size thread") +} + +/// In --release builds, don't increase the stack size. Run the test normally. +/// This way, we find out if any of our tests are blowing the stack even after +/// optimizations in release builds. +#[allow(dead_code)] +#[cfg(not(debug_assertions))] +#[inline(always)] +pub fn with_larger_debug_stack(run_test: F) +where + F: FnOnce() -> (), + F: Send, + F: 'static, +{ + run_test() +} diff --git a/compiler/gen_wasm/tests/wasm_num.rs b/compiler/gen_wasm/tests/wasm_num.rs new file mode 100644 index 0000000000..d1c0ae0d75 --- /dev/null +++ b/compiler/gen_wasm/tests/wasm_num.rs @@ -0,0 +1,863 @@ +#[macro_use] +extern crate pretty_assertions; + +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate libc; + +#[macro_use] +mod helpers; + +#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] +mod dev_num { + #[test] + fn i64_values() { + assert_evals_to!("0", 0, i64); + assert_evals_to!("-0", 0, i64); + assert_evals_to!("-1", -1, i64); + assert_evals_to!("1", 1, i64); + assert_evals_to!("9_000_000_000_000", 9_000_000_000_000, i64); + assert_evals_to!("-9_000_000_000_000", -9_000_000_000_000, i64); + assert_evals_to!("0b1010", 0b1010, i64); + assert_evals_to!("0o17", 0o17, i64); + assert_evals_to!("0x1000_0000_0000_0000", 0x1000_0000_0000_0000, i64); + } + + #[test] + fn f64_values() { + assert_evals_to!("0.0", 0.0, f64); + assert_evals_to!("-0.0", 0.0, f64); + assert_evals_to!("1.0", 1.0, f64); + assert_evals_to!("-1.0", -1.0, f64); + assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64); + assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64); + assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64); + } + + #[test] + fn gen_add_i64() { + assert_evals_to!( + indoc!( + r#" + 1 + 2 + 3 + "# + ), + 6, + i64 + ); + } + + #[test] + fn gen_add_f64() { + assert_evals_to!( + indoc!( + r#" + 1.1 + 2.4 + 3 + "# + ), + 6.5, + f64 + ); + } + + #[test] + fn gen_sub_i64() { + assert_evals_to!( + indoc!( + r#" + 1 - 2 - 3 + "# + ), + -4, + i64 + ); + } + + #[test] + fn gen_mul_i64() { + assert_evals_to!( + indoc!( + r#" + 2 * 4 * 6 + "# + ), + 48, + i64 + ); + } + + #[test] + fn i64_force_stack() { + // This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64. + assert_evals_to!( + indoc!( + r#" + a = 0 + b = 1 + c = 2 + d = 3 + e = 4 + f = 5 + g = 6 + h = 7 + i = 8 + j = 9 + k = 10 + l = 11 + m = 12 + n = 13 + o = 14 + p = 15 + q = 16 + r = 17 + s = 18 + t = 19 + u = 20 + v = 21 + w = 22 + x = 23 + y = 24 + z = 25 + aa = 26 + ab = 27 + ac = 28 + ad = 29 + ae = 30 + af = 31 + ag = 32 + + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag + "# + ), + 528, + i64 + ); + } + + #[test] + fn i64_abs() { + assert_evals_to!("Num.abs -6", 6, i64); + assert_evals_to!("Num.abs 7", 7, i64); + assert_evals_to!("Num.abs 0", 0, i64); + assert_evals_to!("Num.abs -0", 0, i64); + assert_evals_to!("Num.abs -1", 1, i64); + assert_evals_to!("Num.abs 1", 1, i64); + assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64); + assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64); + } + + #[test] + fn gen_int_eq() { + assert_evals_to!( + indoc!( + r#" + 4 == 4 + "# + ), + true, + bool + ); + + assert_evals_to!( + indoc!( + r#" + 3 == 4 + "# + ), + false, + bool + ); + } + + #[test] + fn gen_basic_fn() { + assert_evals_to!( + indoc!( + r#" + always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64) + always42 = \_ -> 42 + + always42 5 + "# + ), + 42, + i64 + ); + } + + #[test] + fn gen_wrap_add_nums() { + assert_evals_to!( + indoc!( + r#" + add2 = \num1, num2 -> num1 + num2 + + add2 4 5 + "# + ), + 9, + i64 + ); + } + + #[test] + fn gen_wrap_add_nums_force_stack() { + assert_evals_to!( + indoc!( + r#" + add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9 + + add9 1 2 3 4 5 6 7 8 9 + "# + ), + 45, + i64 + ); + } + + #[test] + fn pow_int() { + assert_evals_to!("Num.powInt 2 3", 8, i64); + } + + #[test] + fn acos() { + assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64); + } + + #[test] + fn asin() { + assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64); + } + + #[test] + fn atan() { + assert_evals_to!("Num.atan 10", 1.4711276743037347, f64); + } + + #[test] + fn gen_if_fn() { + assert_evals_to!( + indoc!( + r#" + limitedNegate = \num -> + x = + if num == 1 then + -1 + else if num == -1 then + 1 + else + num + x + + limitedNegate 1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn gen_fib_fn() { + assert_evals_to!( + indoc!( + r#" + fib = \n -> + if n == 0 then + 0 + else if n == 1 then + 1 + else + (fib (n - 1)) + (fib (n - 2)) + + fib 10 + "# + ), + 55, + i64 + ); + } + + #[test] + fn f64_abs() { + assert_evals_to!("Num.abs -4.7", 4.7, f64); + assert_evals_to!("Num.abs 5.8", 5.8, f64); + } + + #[test] + fn f64_round() { + assert_evals_to!("Num.round 3.6", 4, i64); + assert_evals_to!("Num.round 3.4", 3, i64); + assert_evals_to!("Num.round 2.5", 3, i64); + assert_evals_to!("Num.round -2.3", -2, i64); + assert_evals_to!("Num.round -2.5", -3, i64); + } + + // #[test] + // fn f64_sqrt() { + // // FIXME this works with normal types, but fails when checking uniqueness types + // assert_evals_to!( + // indoc!( + // r#" + // when Num.sqrt 100 is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // 10.0, + // f64 + // ); + // } + + // #[test] + // fn gen_float_eq() { + // assert_evals_to!( + // indoc!( + // r#" + // 1.0 == 1.0 + // "# + // ), + // true, + // bool + // ); + // } + + // #[test] + // fn gen_div_f64() { + // // FIXME this works with normal types, but fails when checking uniqueness types + // assert_evals_to!( + // indoc!( + // r#" + // when 48 / 2 is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // 24.0, + // f64 + // ); + // } + + // #[test] + // fn gen_int_neq() { + // assert_evals_to!( + // indoc!( + // r#" + // 4 != 5 + // "# + // ), + // true, + // bool + // ); + // } + + // #[test] + // fn gen_wrap_int_neq() { + // assert_evals_to!( + // indoc!( + // r#" + // wrappedNotEq : a, a -> Bool + // wrappedNotEq = \num1, num2 -> + // num1 != num2 + + // wrappedNotEq 2 3 + // "# + // ), + // true, + // bool + // ); + // } + + // #[test] + // fn gen_sub_f64() { + // assert_evals_to!( + // indoc!( + // r#" + // 1.5 - 2.4 - 3 + // "# + // ), + // -3.9, + // f64 + // ); + // } + + // #[test] + // fn gen_div_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when 1000 // 10 is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // 100, + // i64 + // ); + // } + + // #[test] + // fn gen_div_by_zero_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when 1000 // 0 is + // Err DivByZero -> 99 + // _ -> -24 + // "# + // ), + // 99, + // i64 + // ); + // } + + // #[test] + // fn gen_rem_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.rem 8 3 is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // 2, + // i64 + // ); + // } + + // #[test] + // fn gen_rem_div_by_zero_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.rem 8 0 is + // Err DivByZero -> 4 + // Ok _ -> -23 + // "# + // ), + // 4, + // i64 + // ); + // } + + // #[test] + // fn gen_is_zero_i64() { + // assert_evals_to!("Num.isZero 0", true, bool); + // assert_evals_to!("Num.isZero 1", false, bool); + // } + + // #[test] + // fn gen_is_positive_i64() { + // assert_evals_to!("Num.isPositive 0", false, bool); + // assert_evals_to!("Num.isPositive 1", true, bool); + // assert_evals_to!("Num.isPositive -5", false, bool); + // } + + // #[test] + // fn gen_is_negative_i64() { + // assert_evals_to!("Num.isNegative 0", false, bool); + // assert_evals_to!("Num.isNegative 3", false, bool); + // assert_evals_to!("Num.isNegative -2", true, bool); + // } + + // #[test] + // fn gen_is_positive_f64() { + // assert_evals_to!("Num.isPositive 0.0", false, bool); + // assert_evals_to!("Num.isPositive 4.7", true, bool); + // assert_evals_to!("Num.isPositive -8.5", false, bool); + // } + + // #[test] + // fn gen_is_negative_f64() { + // assert_evals_to!("Num.isNegative 0.0", false, bool); + // assert_evals_to!("Num.isNegative 9.9", false, bool); + // assert_evals_to!("Num.isNegative -4.4", true, bool); + // } + + // #[test] + // fn gen_is_zero_f64() { + // assert_evals_to!("Num.isZero 0", true, bool); + // assert_evals_to!("Num.isZero 0_0", true, bool); + // assert_evals_to!("Num.isZero 0.0", true, bool); + // assert_evals_to!("Num.isZero 1", false, bool); + // } + + // #[test] + // fn gen_is_odd() { + // assert_evals_to!("Num.isOdd 4", false, bool); + // assert_evals_to!("Num.isOdd 5", true, bool); + // } + + // #[test] + // fn gen_is_even() { + // assert_evals_to!("Num.isEven 6", true, bool); + // assert_evals_to!("Num.isEven 7", false, bool); + // } + + // #[test] + // fn sin() { + // assert_evals_to!("Num.sin 0", 0.0, f64); + // assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64); + // } + + // #[test] + // fn cos() { + // assert_evals_to!("Num.cos 0", 1.0, f64); + // assert_evals_to!("Num.cos 3.14159265359", -1.0, f64); + // } + + // #[test] + // fn tan() { + // assert_evals_to!("Num.tan 0", 0.0, f64); + // assert_evals_to!("Num.tan 1", 1.557407724654902, f64); + // } + + // #[test] + // fn lt_i64() { + // assert_evals_to!("1 < 2", true, bool); + // assert_evals_to!("1 < 1", false, bool); + // assert_evals_to!("2 < 1", false, bool); + // assert_evals_to!("0 < 0", false, bool); + // } + + // #[test] + // fn lte_i64() { + // assert_evals_to!("1 <= 1", true, bool); + // assert_evals_to!("2 <= 1", false, bool); + // assert_evals_to!("1 <= 2", true, bool); + // assert_evals_to!("0 <= 0", true, bool); + // } + + // #[test] + // fn gt_i64() { + // assert_evals_to!("2 > 1", true, bool); + // assert_evals_to!("2 > 2", false, bool); + // assert_evals_to!("1 > 1", false, bool); + // assert_evals_to!("0 > 0", false, bool); + // } + + // #[test] + // fn gte_i64() { + // assert_evals_to!("1 >= 1", true, bool); + // assert_evals_to!("1 >= 2", false, bool); + // assert_evals_to!("2 >= 1", true, bool); + // assert_evals_to!("0 >= 0", true, bool); + // } + + // #[test] + // fn lt_f64() { + // assert_evals_to!("1.1 < 1.2", true, bool); + // assert_evals_to!("1.1 < 1.1", false, bool); + // assert_evals_to!("1.2 < 1.1", false, bool); + // assert_evals_to!("0.0 < 0.0", false, bool); + // } + + // #[test] + // fn lte_f64() { + // assert_evals_to!("1.1 <= 1.1", true, bool); + // assert_evals_to!("1.2 <= 1.1", false, bool); + // assert_evals_to!("1.1 <= 1.2", true, bool); + // assert_evals_to!("0.0 <= 0.0", true, bool); + // } + + // #[test] + // fn gt_f64() { + // assert_evals_to!("2.2 > 1.1", true, bool); + // assert_evals_to!("2.2 > 2.2", false, bool); + // assert_evals_to!("1.1 > 2.2", false, bool); + // assert_evals_to!("0.0 > 0.0", false, bool); + // } + + // #[test] + // fn gte_f64() { + // assert_evals_to!("1.1 >= 1.1", true, bool); + // assert_evals_to!("1.1 >= 1.2", false, bool); + // assert_evals_to!("1.2 >= 1.1", true, bool); + // assert_evals_to!("0.0 >= 0.0", true, bool); + // } + + // #[test] + // fn gen_order_of_arithmetic_ops() { + // assert_evals_to!( + // indoc!( + // r#" + // 1 + 3 * 7 - 2 + // "# + // ), + // 20, + // i64 + // ); + // } + + // #[test] + // fn gen_order_of_arithmetic_ops_complex_float() { + // assert_evals_to!( + // indoc!( + // r#" + // 3 - 48 * 2.0 + // "# + // ), + // -93.0, + // f64 + // ); + // } + + // #[test] + // fn if_guard_bind_variable_false() { + // assert_evals_to!( + // indoc!( + // r#" + // wrapper = \{} -> + // when 10 is + // x if x == 5 -> 0 + // _ -> 42 + + // wrapper {} + // "# + // ), + // 42, + // i64 + // ); + // } + + // #[test] + // fn if_guard_bind_variable_true() { + // assert_evals_to!( + // indoc!( + // r#" + // wrapper = \{} -> + // when 10 is + // x if x == 10 -> 42 + // _ -> 0 + + // wrapper {} + // "# + // ), + // 42, + // i64 + // ); + // } + + // #[test] + // fn tail_call_elimination() { + // assert_evals_to!( + // indoc!( + // r#" + // sum = \n, accum -> + // when n is + // 0 -> accum + // _ -> sum (n - 1) (n + accum) + + // sum 1_000_000 0 + // "# + // ), + // 500000500000, + // i64 + // ); + // } + + // #[test] + // fn int_negate() { + // assert_evals_to!("Num.neg 123", -123, i64); + // } + + // #[test] + // fn gen_wrap_int_neg() { + // assert_evals_to!( + // indoc!( + // r#" + // wrappedNeg = \num -> -num + + // wrappedNeg 3 + // "# + // ), + // -3, + // i64 + // ); + // } + + // #[test] + // fn int_to_float() { + // assert_evals_to!("Num.toFloat 0x9", 9.0, f64); + // } + + // #[test] + // fn num_to_float() { + // assert_evals_to!("Num.toFloat 9", 9.0, f64); + // } + + // #[test] + // fn float_to_float() { + // assert_evals_to!("Num.toFloat 0.5", 0.5, f64); + // } + + // #[test] + // fn int_compare() { + // assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder); + // assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder); + // assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder); + // } + + // #[test] + // fn float_compare() { + // assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder); + // assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder); + // assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder); + // } + + // #[test] + // fn pow() { + // assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64); + // } + + // #[test] + // fn ceiling() { + // assert_evals_to!("Num.ceiling 1.1", 2, i64); + // } + + // #[test] + // fn floor() { + // assert_evals_to!("Num.floor 1.9", 1, i64); + // } + + // // #[test] + // // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)] + // // fn int_overflow() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // 9_223_372_036_854_775_807 + 1 + // // "# + // // ), + // // 0, + // // i64 + // // ); + // // } + + // #[test] + // fn int_add_checked() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.addChecked 1 2 is + // Ok v -> v + // _ -> -1 + // "# + // ), + // 3, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // when Num.addChecked 9_223_372_036_854_775_807 1 is + // Err Overflow -> -1 + // Ok v -> v + // "# + // ), + // -1, + // i64 + // ); + // } + + // #[test] + // fn int_add_wrap() { + // assert_evals_to!( + // indoc!( + // r#" + // Num.addWrap 9_223_372_036_854_775_807 1 + // "# + // ), + // std::i64::MIN, + // i64 + // ); + // } + + // #[test] + // fn float_add_checked_pass() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.addChecked 1.0 0.0 is + // Ok v -> v + // Err Overflow -> -1.0 + // "# + // ), + // 1.0, + // f64 + // ); + // } + + // #[test] + // fn float_add_checked_fail() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is + // Err Overflow -> -1 + // Ok v -> v + // "# + // ), + // -1.0, + // f64 + // ); + // } + + // // #[test] + // // #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)] + // // fn float_overflow() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // 1.7976931348623157e308 + 1.7976931348623157e308 + // // "# + // // ), + // // 0.0, + // // f64 + // // ); + // // } + + // #[test] + // fn max_i128() { + // assert_evals_to!( + // indoc!( + // r#" + // Num.maxI128 + // "# + // ), + // i128::MAX, + // i128 + // ); + // } + + // #[test] + // fn num_max_int() { + // assert_evals_to!( + // indoc!( + // r#" + // Num.maxInt + // "# + // ), + // i64::MAX, + // i64 + // ); + // } + + // #[test] + // fn num_min_int() { + // assert_evals_to!( + // indoc!( + // r#" + // Num.minInt + // "# + // ), + // i64::MIN, + // i64 + // ); + // } +} diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs new file mode 100644 index 0000000000..2fcebe8c97 --- /dev/null +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -0,0 +1,937 @@ +#[macro_use] +extern crate indoc; + +#[macro_use] +mod helpers; + +#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] +mod dev_records { + #[test] + fn basic_record() { + assert_evals_to!( + indoc!( + r#" + { y: 17, x: 15, z: 19 }.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: 17, z: 19 }.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: 17, z: 19 }.z + "# + ), + 19, + i64 + ); + } + + #[test] + fn nested_record() { + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a + "# + ), + 12, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c + "# + ), + 2, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z + "# + ), + 19, + i64 + ); + } + + #[test] + fn f64_record() { + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.x + "# + ), + 15.1, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.y + "# + ), + 17.2, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.z + "# + ), + 19.3, + f64 + ); + } + + // #[test] + // fn fn_record() { + // assert_evals_to!( + // indoc!( + // r#" + // getRec = \x -> { y: 17, x, z: 19 } + + // (getRec 15).x + // "# + // ), + // 15, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // rec = { x: 15, y: 17, z: 19 } + + // rec.y + // "# + // ), + // 17, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // rec = { x: 15, y: 17, z: 19 } + + // rec.z + // "# + // ), + // 19, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // rec = { x: 15, y: 17, z: 19 } + + // rec.z + rec.x + // "# + // ), + // 34, + // i64 + // ); + // } + + #[test] + fn def_record() { + assert_evals_to!( + indoc!( + r#" + rec = { y: 17, x: 15, z: 19 } + + rec.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.z + "# + ), + 19, + i64 + ); + } + + #[test] + fn when_on_record() { + assert_evals_to!( + indoc!( + r#" + when { x: 0x2 } is + { x } -> x + 3 + "# + ), + 5, + i64 + ); + } + + #[test] + fn when_record_with_guard_pattern() { + assert_evals_to!( + indoc!( + r#" + when { x: 0x2, y: 3.14 } is + { x: var } -> var + 3 + "# + ), + 5, + i64 + ); + } + + #[test] + fn let_with_record_pattern() { + assert_evals_to!( + indoc!( + r#" + { x } = { x: 0x2, y: 3.14 } + + x + "# + ), + 2, + i64 + ); + } + + #[test] + fn record_guard_pattern() { + assert_evals_to!( + indoc!( + r#" + when { x: 0x2, y: 3.14 } is + { x: 0x4 } -> 5 + { x } -> x + 3 + "# + ), + 5, + i64 + ); + } + + #[test] + fn twice_record_access() { + assert_evals_to!( + indoc!( + r#" + x = {a: 0x2, b: 0x3 } + + x.a + x.b + "# + ), + 5, + i64 + ); + } + #[test] + fn empty_record() { + assert_evals_to!( + indoc!( + r#" + v = {} + + v + "# + ), + (), + () + ); + } + + #[test] + fn i64_record1_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3 } + "# + ), + 3, + i64 + ); + } + + // #[test] + // fn i64_record2_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3, y: 5 } + // "# + // ), + // (3, 5), + // (i64, i64) + // ); + // } + + // // #[test] + // // fn i64_record3_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // { x: 3, y: 5, z: 17 } + // // "# + // // ), + // // (3, 5, 17), + // // (i64, i64, i64) + // // ); + // // } + + // #[test] + // fn f64_record2_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3.1, y: 5.1 } + // "# + // ), + // (3.1, 5.1), + // (f64, f64) + // ); + // } + + // // #[test] + // // fn f64_record3_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // { x: 3.1, y: 5.1, z: 17.1 } + // // "# + // // ), + // // (3.1, 5.1, 17.1), + // // (f64, f64, f64) + // // ); + // // } + + // // #[test] + // // fn bool_record4_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // record : { a : Bool, b : Bool, c : Bool, d : Bool } + // // record = { a: True, b: True, c : True, d : Bool } + + // // record + // // "# + // // ), + // // (true, false, false, true), + // // (bool, bool, bool, bool) + // // ); + // // } + + // #[test] + // fn i64_record1_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3 } + // "# + // ), + // 3, + // i64 + // ); + // } + + // // #[test] + // // fn i64_record9_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } + // // "# + // // ), + // // (3, 5, 17, 1, 9, 12, 13, 14, 15), + // // (i64, i64, i64, i64, i64, i64, i64, i64, i64) + // // ); + // // } + + // // #[test] + // // fn f64_record3_literal() { + // // assert_evals_to!( + // // indoc!( + // // r#" + // // { x: 3.1, y: 5.1, z: 17.1 } + // // "# + // // ), + // // (3.1, 5.1, 17.1), + // // (f64, f64, f64) + // // ); + // // } + + // #[test] + // fn bool_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // x : Bool + // x = True + + // x + // "# + // ), + // true, + // bool + // ); + // } + + // #[test] + // fn optional_field_when_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + + // main = + // a = f { x: Blue, y: 7 } + // b = f { x: Blue } + // c = f { x: Red, y: 11 } + // d = f { x: Red } + + // a * b * c * d + // "# + // ), + // 3 * 5 * 7 * 11, + // i64 + // ); + // } + + // #[test] + // fn optional_field_when_use_default_nested() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + + // a = f { x: Blue, y: 7 } + // b = f { x: Blue } + // c = f { x: Red, y: 11 } + // d = f { x: Red } + + // a * b * c * d + // "# + // ), + // 3 * 5 * 7 * 11, + // i64 + // ); + // } + + // #[test] + // fn optional_field_when_no_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \r -> + // { x ? 10, y } = r + // x + y + + // main = + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_when_no_use_default_nested() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // { x ? 10, y } = r + // x + y + + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_let_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \r -> + // { x ? 10, y } = r + // x + y + + // main = + // f { y: 9 } + // "# + // ), + // 19, + // i64 + // ); + // } + + // #[test] + // fn optional_field_let_no_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \r -> + // { x ? 10, y } = r + // x + y + + // main = + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_let_no_use_default_nested() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // { x ? 10, y } = r + // x + y + + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_function_use_default() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \{ x ? 10, y } -> x + y + + // f { y: 9 } + // "# + // ), + // 19, + // i64 + // ); + // } + + // #[test] + // #[ignore] + // fn optional_field_function_no_use_default() { + // // blocked on https://github.com/rtfeldman/roc/issues/786 + // assert_evals_to!( + // indoc!( + // r#" + // app "test" provides [ main ] to "./platform" + + // f = \{ x ? 10, y } -> x + y + + // main = + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // #[ignore] + // fn optional_field_function_no_use_default_nested() { + // // blocked on https://github.com/rtfeldman/roc/issues/786 + // assert_evals_to!( + // indoc!( + // r#" + // f = \{ x ? 10, y } -> x + y + + // f { x: 4, y: 9 } + // "# + // ), + // 13, + // i64 + // ); + // } + + // #[test] + // fn optional_field_singleton_record() { + // assert_evals_to!( + // indoc!( + // r#" + // when { x : 4 } is + // { x ? 3 } -> x + // "# + // ), + // 4, + // i64 + // ); + // } + + // #[test] + // fn optional_field_empty_record() { + // assert_evals_to!( + // indoc!( + // r#" + // when { } is + // { x ? 3 } -> x + // "# + // ), + // 3, + // i64 + // ); + // } + + // #[test] + // fn return_record_2() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3, y: 5 } + // "# + // ), + // [3, 5], + // [i64; 2] + // ); + // } + + // #[test] + // fn return_record_3() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3, y: 5, z: 4 } + // "# + // ), + // (3, 5, 4), + // (i64, i64, i64) + // ); + // } + + // #[test] + // fn return_record_4() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 4, d: 2 } + // "# + // ), + // [3, 5, 4, 2], + // [i64; 4] + // ); + // } + + // #[test] + // fn return_record_5() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 4, d: 2, e: 1 } + // "# + // ), + // [3, 5, 4, 2, 1], + // [i64; 5] + // ); + // } + + // #[test] + // fn return_record_6() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } + // "# + // ), + // [3, 5, 4, 2, 1, 7], + // [i64; 6] + // ); + // } + + // #[test] + // fn return_record_7() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } + // "# + // ), + // [3, 5, 4, 2, 1, 7, 8], + // [i64; 7] + // ); + // } + + // #[test] + // fn return_record_float_int() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3.14, b: 0x1 } + // "# + // ), + // (3.14, 0x1), + // (f64, i64) + // ); + // } + + // #[test] + // fn return_record_int_float() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 0x1, b: 3.14 } + // "# + // ), + // (0x1, 3.14), + // (i64, f64) + // ); + // } + + // #[test] + // fn return_record_float_float() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 6.28, b: 3.14 } + // "# + // ), + // (6.28, 3.14), + // (f64, f64) + // ); + // } + + // #[test] + // fn return_record_float_float_float() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 6.28, b: 3.14, c: 0.1 } + // "# + // ), + // (6.28, 3.14, 0.1), + // (f64, f64, f64) + // ); + // } + + // #[test] + // fn return_nested_record() { + // assert_evals_to!( + // indoc!( + // r#" + // { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } } + // "# + // ), + // (0x0, (6.28, 3.14, 0.1)), + // (i64, (f64, f64, f64)) + // ); + // } + + // #[test] + // fn accessor() { + // assert_evals_to!( + // indoc!( + // r#" + // .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 } + // "# + // ), + // 7, + // i64 + // ); + // } + + // #[test] + // fn accessor_single_element_record() { + // assert_evals_to!( + // indoc!( + // r#" + // .foo { foo: 4 } + // "# + // ), + // 4, + // i64 + // ); + // } + + // #[test] + // fn update_record() { + // assert_evals_to!( + // indoc!( + // r#" + // rec = { foo: 42, bar: 6 } + + // { rec & foo: rec.foo + 1 } + // "# + // ), + // (6, 43), + // (i64, i64) + // ); + // } + + #[test] + fn update_single_element_record() { + assert_evals_to!( + indoc!( + r#" + rec = { foo: 42} + + { rec & foo: rec.foo + 1 } + "# + ), + 43, + i64 + ); + } + + // #[test] + // fn booleans_in_record() { + // assert_evals_to!( + // indoc!("{ x: 1 == 1, y: 1 == 1 }"), + // (true, true), + // (bool, bool) + // ); + // assert_evals_to!( + // indoc!("{ x: 1 != 1, y: 1 == 1 }"), + // (false, true), + // (bool, bool) + // ); + // assert_evals_to!( + // indoc!("{ x: 1 == 1, y: 1 != 1 }"), + // (true, false), + // (bool, bool) + // ); + // assert_evals_to!( + // indoc!("{ x: 1 != 1, y: 1 != 1 }"), + // (false, false), + // (bool, bool) + // ); + // } + + // #[test] + // fn alignment_in_record() { + // assert_evals_to!( + // indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"), + // (32i64, true, 2u8), + // (i64, bool, u8) + // ); + // } + + // #[test] + // fn blue_and_present() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + + // f { x: Blue, y: 7 } + // "# + // ), + // 7, + // i64 + // ); + // } + + // #[test] + // fn blue_and_absent() { + // assert_evals_to!( + // indoc!( + // r#" + // f = \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + + // f { x: Blue } + // "# + // ), + // 3, + // i64 + // ); + // } +}